Written by nthistle
It seems like the TJCTF organizers are secretly running an underground virtual horse racing platform! They call it 'Moar Horse 4'... See if you can get a flag from it!
Source
https://moar_horse.tjctf.org/
薄暗い怪しいサイトが出てくる。
適当にボタンを押すと、いつものアレが流れるので、これをBGMにして解く。
馬のレースをするサイトのようだ。
なぜか、ffアドベンチャーを思い出した。
調査
- サイト構成
/
特に何もできない/race
所有している馬がいればレースを仕掛けられる/store
馬が買える
- Responseから得られる情報
- cookieでtokenを入れ込んでいっている
- いつものJWT
{ "user": true, "is_omkar": false, "money": 0, "horses": [ "Luke" ] }
- いつものJWT
- cookieでtokenを入れ込んでいっている
- 添付されているソースコードから得られる情報
- templates/index.html
Money: ${{money}}
- SSTIか?
- templates/new_user.html
- 最初の画面か。ボタンを押すと
/flag
へGETリクエストを飛ばす
- 最初の画面か。ボタンを押すと
- templates/race.html
/do_race?horse={{horse}}
くらいがきになる所か
- templates/race_results.html
- レースの結果が表示される
- templates/store.html
- はい
/buy_horse?id={{horse['id']}}
へのリクエストが見えますね
- horse_names.txt
- 沢山の馬の名前がある。他にも強い馬がいるかもしれない
- pubkey.pem
- server.pyから参照されている。JWTの署名に使われている
- private keyは見当たらない。JWTの書き換えは難しそうだ
- server.py
- 気になる部分を以下に羅列しておこう
- validate_token関数
- JWTにuser,is_omkar,money,horsesがあるか確認している
- is_omkarをtrueにできれば勝ち? → いや、他で使われてないな
- do_race関数
boss_speed = int(hashlib.md5(("Horse_" + BOSS_HORSE).encode()).hexdigest(), 16)
your_speed = int(hashlib.md5(("Horse_" + race_horse).encode()).hexdigest(), 16)
- これでスピード計算をしている
- templates/index.html
do_race関数
@app.route("/do_race") def do_race(): if "token" in request.cookies: is_valid, data = validate_token(request.cookies["token"]) if is_valid: if "horse" in request.args: race_horse = request.args.get("horse") else: return redirect("/race") owned_horses = data["horses"] if race_horse not in owned_horses: return redirect("/race?error") boss_speed = int(hashlib.md5(("Horse_" + BOSS_HORSE).encode()).hexdigest(), 16) your_speed = int(hashlib.md5(("Horse_" + race_horse).encode()).hexdigest(), 16) if your_speed > boss_speed: return render_template("race_results.html", money=data["money"], victory=True, flag=flag) else: return render_template("race_results.html", money=data["money"], victory=False) else: return redirect("/") else: return redirect("/")
ここを突破できればいいので、細かく考えていこう。
念のため、与えられてる馬で勝てる馬があるか探してみる。
import hashlib BOSS_HORSE = "MechaOmkar-YG6BPRJM" boss_speed = int(hashlib.md5(("Horse_" + BOSS_HORSE).encode()).hexdigest(), 16) horses = ["Luke", "Noah", "Liam", "Michael", "Alexander", "Ethan", "William", "James", "Logan", "Benjamin", "Mason", "Elijah", "Darin", "Oliver", "Jacob", "Lucas", "Daniel", "Matthew", "Aiden", "Henry", "Joseph", "Jackson", "defund"] for horse in horses: your_speed = int(hashlib.md5(("Horse_" + horse).encode()).hexdigest(), 16) if your_speed > boss_speed: print(f"win! by {horse}") else: print("lose.....")
全敗。まあ、そりゃそうか。
となると、horsesに任意の勝てる馬を埋め込んで、勝つしかないな。
JWTの書き換えできる…?
勝てる馬の名前をブルートフォースで探す。
なかなか見つからないが、*gwsqb
なら勝てるようだ。
JWTを書き換える
RS256で署名されていて、公開鍵が分かっている状態であれば、
認証方式をHS256に変更してクラックするテクニックがある。
3v4Si0N/RS256-2-HS256: JWT Attack to change the algorithm RS256 to HS256
PoCコードがあるので、使用してみよう。
おお。これを入れると、最強の馬が手に入る。
You won!