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
を食わせられないかなと思ったらできた。