Writeup - Flatt Security Developers' Quiz #6

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 環境変数にセットしてある。

解答

  1. 任意のユーザー名 (myuser とする) と任意のセッションID (mysession とする) でユーザー作成 & ログイン

  2. 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
2
irb(main):027:0> JSON.parse('{"username":"\\admin"}')['username']
=> "admin"

この挙動を利用して愚直に {"username":"\\admin"} みたいなリクエストを送ったりもしたが、バックスラッシュがGoでのJSONエンコーディング時点で増幅したりしてうまく行かず(それが正しいJSONライブラリの挙動なのでそれはそう)。

ここでめちゃくちゃウンウン唸ってしまったが、ふとJSONキーを重複させる戦略を思いつく。

解答リクエストボディ
1
{"username":"myuser","username":"admi\u006e"}

とやって、Goには一個目の作成済みのユーザー名を食わせつつ、Rubyには二個目の admin を食わせられないかなと思ったらできた。

author Sho Nakatani a.k.a. laysakura

トヨタ自動車株式会社所属。プリンシパル・リサーチャーとして、セキュリティ・プライバシー・データ基盤に関する業務に従事。
OSCP/BSCP/CISSP/情報処理安全確保支援士(合格) 等の資格保有。CTF出場やセキュリティ関連の講演活動も行っている。
詳細プロフィール