Writeup - Flatt Security Developers' Quiz #6
⚡️ Flatt Security Developers' Quiz #6 開催! ⚡️
— 株式会社Flatt Security (@flatt_security) December 29, 2023
解答は年明け1/5(金)11:59まで!Tシャツ獲得を目指して頑張ってください!
デモ環境: https://t.co/hXaNP2Ciwv
ソースコード: https://t.co/ejTKzpAp9D
解答提出フォーム: https://t.co/jnc5Wv2Hi7 pic.twitter.com/uf3ZqHEdTK
Flatt Security Developers’ Quiz #6 に回答し、Tシャツ頂きました👕
Writeup書きます。
目次
問題
https://github.com/flatt-security/developers-quiz/tree/main/quiz6 のリポジトリのコードと実稼働しているデモ環境を見てフラグを探す形式。
compose.yml によると、上流から
- Nginx
- APIサーバー (Go)
- レガシーAPIサーバー (Ruby)
の三層構造。フラグはレガシーAPIサーバーの FLAG 環境変数にセットしてある。
解答
任意のユーザー名 (
myuserとする) と任意のセッションID (mysessionとする) でユーザー作成 & ログインPOST /resultに、以下JSONをリクエストボディとしてリクエスト送信:解答リクエストボディ 1
{"username":"myuser","username":"admi\u006e"}
解答のポイント
app.rb を見ると、 username["admin"] にフラグ文字列がセットされている。POST /result に {"username":"admin"} を送信するとフラグがレスポンスで返ってくるのが本質だが、前段のGoサーバーでいくつか admin ユーザーによる操作をブロックされている。
GoとRubyのJSONパーサーのパースロジックの差異を突いて、Goには admin に見えず Ruby には admin に見えるリクエストを送信するのがポイント。
解答までの道筋
Goではセッション管理と admin ブロックを主にしていて、Rubyで投票結果(とフラグ)を管理している。
Goでの admin ブロックについて。
ユーザー登録では admin 文字列は拒否されている。JSONなのでUnicodeエンコードなどは有効になり得るが、JSONパース後に admin 文字列との一致を見られているので、回避不可。
1 | {"username":"admi\u006e"} |
POST /result では、JSONパースする前のリクエストボディをバイト列としてみて、 admin が含まれているかをチェックしている。これはUnicodeエンコードなどで回避可能。
だからといって単純に
1 | {"username":"admi\u006e"} |
のようなリクエストを送っても、Goのレイヤーで「admin ユーザーは作成されてない」とエラーになってしまう。
Unicodeエンコード以外に、RubyのJSONパーサーでだけ admin に解釈される何かがないかを探り始める。
Rubyで使われているJSONパーサーの https://github.com/flori/json/blob/master/lib/json/pure/parser.rb の実装を見つつ、\\ が消えることを発見した。
1 | JSON.parse('{"username":"\\admin"}')['username'] |
この挙動を利用して愚直に {"username":"\\admin"} みたいなリクエストを送ったりもしたが、バックスラッシュがGoでのJSONエンコーディング時点で増幅したりしてうまく行かず(それが正しいJSONライブラリの挙動なのでそれはそう)。
ここでめちゃくちゃウンウン唸ってしまったが、ふとJSONキーを重複させる戦略を思いつく。
1 | {"username":"myuser","username":"admi\u006e"} |
とやって、Goには一個目の作成済みのユーザー名を食わせつつ、Rubyには二個目の admin を食わせられないかなと思ったらできた。