よーでんのブログ

One for All,All for わんわんお!

IPFactory Welcome CTF 2022 - Web writeup

IPFactory Welcome CTF 2022

新入生向けのCTFをIPFactoryで開催。私はまたWebの作問をしました。
今年も初心者に楽しんでもらえるように、スコープを狭くして、問題の誘導も結構頑張って考えました。
今年はヒントも結構考えています。

難易度の想定は以下のような感じ。
hardも1問作ってはいましたが、easy < medium <<<<<<<<<<<<<<<<< hard みたいになってたので没にしました。

難易度 対象
easy セキュリティ初めての人向け。調べたらすぐわかるくらい。
medium ちょっと頭を使う問題 ~ ソースを読まなきゃ解けない

Webは全4問、1問は後輩に作問してもらいました。
ソースはflag以外全配布、先日のWSL2環境構築会に参加してくれた一年生はDockerを利用してローカルで検証もできるようにしました。

Web writeup

THE WORLD - warmup, easy 200pt ( 7 Solves )

あ...ありのまま 今 起こった事を話すぜ!

『おれはやつの前で階段を登っていた
と思ったらいつのまにか降りていた』

Hint 1

/upにアクセスした際に、強制的に/downにリダイレクトさせられています。
ブラウザのリダイレクトの仕組みはどうなっているでしょうか。調べてみましょう。

Hint 2

/THE WORLD/python/src/main.py の12行目から16行目が/upの時に動くソースです。
リダイレクトに必要なLocationヘッダーの他にもヘッダを追加していますね。ヘッダーはどうやって見れば良いでしょうか。

welcome問題です。
アクセスすると以下のような表示。

THE WORLD - TOP

登る・降りるのリンクがあり、それぞれ/up, /downに遷移します。

<a href="/up">登る</a> / <a href="/down">降りる</a><br>

階段を登ればflagをくれるようなので、登ってみましょう。

THE WORLD - down

『おれはやつの前で階段を登っていたと思ったらいつのまにか降りていた』というやつです。
確かに/upに飛んだはずですが、URLバーを見るとなぜか/down に居ます。

THE WORLD - down

/up にアクセスした際に動くコードを見てみましょう。

@app.route("/up")
def theworld():
    resp = make_response()
    resp.headers['Flag'] = os.environ['flag']
    resp.headers['Location'] = '/down'
    return resp, 302

resp.headers['Location'] = '/down' という部分の所為で/downにリダイレクトさせられてしまっています。
他に、resp.headers['Flag'] = os.environ['flag'] というのもありますね。
/down へのリダイレクト命令の他にも、flagがくっついているようです。

resp.headers 等で調べると、どうやらHTTPヘッダーというものらしいことがわかります。
HTTPヘッダーを見る方法があればflagが見れそうです。

HTTPヘッダーを見る方法はいろいろありますが、今回はブラウザで見る方法をやってみます。

ブラウザにフォーカスしている状態でF12キーを押すと、「開発者ツール」が開けます。
Networkというタブを開いた状態で、トップページの"登る"リンクをクリックしてみましょう。

THE WORLD - Network

/up/down のログがでてきます。今見たいのは/upの時のレスポンスなので、/upをクリック。

THE WORLD - flag

Response Headersの中にLocation: /downFlag: ...があります。

flag{Or3d4k3n0_Z1k4nd4z3}

CVE-2021-41773 - medium 300pt ( 2 Solves )

去年の年末、apacheに深刻な脆弱性が見つかったと聞きました。。
/flag.txt を見られると困るのですが、大丈夫でしょうか・・・?

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-41773

Hint 1

「CVE-2021-41773 exploit」「CVE-2021-41773 検証」等で調べると情報がたくさんでてきます。 肝心の場所が隠されている事が多いですが、根気強く調べてみましょう。

CVE-2021-41773 - TOP

CVE-2021-41773、つまりapacheパストラバーサルです。
原理を理解しようとするとURLエンコード相対パスの概念を理解する必要がありますが、再現するだけならそんな必要もありません。

「CVE-2021-41773 exploit」とかで根気よく調べると、次のようなペイロードが見つけられると思います。

http://$host/cgi-bin/.%2e/.%2e/.%2e/.%2e/etc/passwd

とりあえずこのペイロードに沿ってリクエストを送信してみましょう。

ブラウザでhttp://localhost:8080/cgi-bin/.%2e/.%2e/.%2e/.%2e/etc/passwdとかにアクセスしても、ブラウザがhttp://localhost:8080/etc/passwdに最適化してしまうのでcurlを使います。
この辺のツールの選択も、調べ方によってはすぐ出るかと思います。

CVE-2021-41773 - /etc/passwd

/etc/passwdが盗れました。
問題文では/flag.txtだと言っているので、/flag.txtを見ます。

CVE-2021-41773 - flag

flagゲット!

flag{4p4ch3_p4th_tr4v3rs4l}

# PIYOTASO - medium 300pt ( 2 Solves )

ひよこの管理者になりましょう。

2年前にISCCTFで出題したcrackjwt のjwtじゃないバージョンです。
今回もぴよたそ の画像をお借りしています。

PIYOTASO - TOP

適当に名前を入力してログインすると、flagを閲覧する権限がないと言われてしまいます。

PIYOTASO - yoden

該当コードを見てみましょう。

<?php
$status = json_decode('{"isAdmin": false, "name": "' . $_POST['name'] . '"}', true);
if ($status["isAdmin"] == false) {
    echo '<img src="/gif/nyoronyoro.gif" alt="nyoronyoro"><p>ようこそ ' . htmlspecialchars($status["name"]) . 'さん。<br>あなたにはflagを閲覧する権限はありません。</p>';
} else {
    echo '<img src="/gif/congrats.gif" alt="congrats"><p>ようこそ ' . htmlspecialchars($status["name"]) . 'さん。<br><strong>' . $_ENV["flag"] . '</strong></p>';
}
?>

json_decode('{"isAdmin": false, "name": "' . $_POST['name'] . '"}', true);というところがミソです。
yodenと入力した際のjsonは以下のようになります。

{"isAdmin": false, "name": "yoden"}

isAdminがfalseなため閲覧権限がなく、nyoronyoro.gifが返ってきます。

この情報をヒントにするか迷ったのですが、PHPjson_decodeではdecode時にkeyが衝突したら一番最後の値が優先されます
つまり以下のようなjsonを作ることができたら、json_decode時にisAdminがtrueになります。

{"isAdmin": false, "name": "yoden", "isAdmin": true}

ログイン時の入力を工夫してみましょう。
まずは"のみを入力した際はどうなるか考えてみます。

json{"isAdmin": false, "name": "yoden""}のようになり、パースはエラーになります。

PIYOTASO - "

これを利用して{"isAdmin": false, "name": "yoden", "isAdmin": true}のようなjsonを作りたいところですが、 yoden", "isAdmin": trueのようにしてしまうと、実際に生成されるjsonは以下のようになり、またエラーになってしまいます。

{"isAdmin": false, "name": "yoden", "isAdmin": true"}

最後の"が邪魔なので、もう一度"name"を挟むことで解決します。
yoden", "isAdmin": true, "name": "y0d3nを入力することでjsonを以下のようにするとflagが手に入ります。

{"isAdmin": false, "name": "yoden", "isAdmin": true, "name": "y0d3n"}

PIYOTASO - flag

flag{hiyoko_manju_tottemo_oisi}

振り返り

THE WORLD、出オチ感はありますがシナリオとしてピッタリだったのでとても気に入っています。
この問題がマージされるまでが一番大変でした。

PR

ジョジョは見たほうが良いと思う

問題のレビュー時にrequested changtesでこんなことを言われてしまったので、CTFに間に合うように必死にジョジョ3部を見ました。
結構楽しかったです。

ジョジョ

(ネタバレ回避のためにしっかり隠しています。)
見終わった報告をするとApproveされ、最終的にマージされたのは開催二日前。あぶなかった。


今年はとても参加者が少なくてへこんでたのですが、12時間ずっとやってくれてた子もいたおかげでログを見るのは楽しかったです。

Webの全完も二人出て(うち一人は一年生!)、全体的な難易度も丁度良かったかなと思います。
参加してくれた方、ありがとうございました!