Hello there, ('ω')ノ
ねらい
このLABは、検索結果に反射型XSSがある一方で、レスポンスヘッダのCSP(Content-Security-Policy)によって実行が止められます。ところがCSPの中にあるreport-uri の token パラメータがユーザ入力に依存しており、ここからCSPディレクティブを追記注入できます。
狙いは、token に「;script-src-elem 'unsafe-inline'」を差し込んでポリシーを上書きし、インラインスクリプトを許可させて alert(1) を出すこと。
※ この解法はChrome前提です(LABの注記どおり)。
全体像(まずはストーリー)
- 検索欄に <img src=1 onerror=alert(1)> を入れて反射を確認 → CSPでブロック。
- BurpでレスポンスヘッダContent-Security-Policyを見ると、report-uriにtoken=…が付与されており、この token がURLのクエリから取り込まれていると分かる。
- “;”(セミコロン)はCSPでディレクティブ区切り。よって token に ;script-src-elem 'unsafe-inline' を注入すれば、新しいディレクティブを丸ごと追加できる。
- script-src-elem はscript要素に限定して適用され、既存の script-src より優先される(Chrome実装に依存)。
- 最後に検索語へ<script>alert(1)</script>を反射させると、インライン許可により実行される。
実践:一手ずつ「なぜそうするか」を添えて
1) 反射XSSの有無とCSPの妨害を確かめる
- 操作:検索欄に以下を入力し、送信。
<img src=1 onerror=alert(1)>
- 観察:HTMLには反射されるが、CSP違反でアラートは出ない。
- なぜ:CSPの script-src / script-src-elem などがインライン実行を禁止しているため。
2) CSPの“注入点”を見つける
- 操作:BurpでレスポンスのContent-Security-Policyを確認。
- 観察:ポリシー末尾にreport-uri /csp-report?token=... のような記述。かつtokenがURLクエリから未エスケープで合成されている。
- なぜ:このtokenにセミコロンを混ぜられると、CSPパーサは新しいディレクティブだと解釈する。つまりヘッダインジェクションと同義の失敗。
3) “;script-src-elem 'unsafe-inline'” を差し込む
- 操作:次のようなURLでアクセス(YOUR-LAB-IDは自分のに差し替え)。
https://YOUR-LAB-ID.web-security-academy.net/
?search=%3Cscript%3Ealert%281%29%3C%2Fscript%3E
&token=;script-src-elem%20%27unsafe-inline%27
※ 改行せず1行で。<script>alert(1)</script> はURLエンコード済み。
観察:CSPヘッダ内の
... report-uri /csp-report?token=;script-src-elem 'unsafe-inline'以降が、別ディレクティブとして解釈される。なぜ:script-src-elem は scriptタグに対するポリシーを個別に制御し、Chromeではここに 'unsafe-inline' を付与するとインライン実行が許可される(LABの“Chromeのみ”という条件がここ)。
4) アラート発火でクリア
- 操作:ページを読み込むと、反射された
<script>alert(1)</script>
が実行され、alert(1) が表示される。
- 理屈:本来の script-src ではインライン禁止でも、script-src-elem 'unsafe-inline' が後勝ちで上書きされるため。
つまずきポイント&対処
アラートが出ない
- URLのエンコード漏れがないか確認(特にクォートやスペース)。
- token= に入れるのは 先頭にセミコロン を付けた ;script-src-elem%20%27unsafe-inline%27。
- ブラウザはChromeを使用(LAB要件)。他ブラウザだと解釈や優先度が異なり動かないことがある。
scriptタグでなくimg onerrorでやりたい
- 今回はscript-src-elemを使う都合上、script要素での実行が確実。onerrorはハンドラ扱いで、ポリシーの影響が異なる場合がある。
なぜ成立するのか(根本理解)
- CSPは“ディレクティブの配列”。セミコロン; で区切られ、左から順に解釈される。
- report-uri の tokenをURLからそのままヘッダに連結すると、;xxx を混ぜられた時点で新ディレクティブが生える。
- script-src-elem は script-src より粒度が細かく、適用対象が重なる場合は後者を上書きする(Chromeの実装準拠)。
- 結果、'unsafe-inline' が有効化され、インラインscriptが動く。
実務目線の防御
- ヘッダにユーザ入力を混ぜない:どうしても必要なら厳格なエンコード(セミコロン/改行/クォート禁止)。
CSPは厳格に:
- nonce もしくは hash('sha256-…')でインライン禁止を徹底。
- script-src-elem と script-src の両方を意識して設計。
- report-uri / report-to の扱い:パラメータを固定、動的に組み立てない。
- CSP評価のクロスブラウザ検証:ブラウザ間挙動差を前提に、最もゆるい解釈でも破れないポリシーに。
コピペ用URL(自分のLAB IDに差し替え)
https://YOUR-LAB-ID.web-security-academy.net/?search=%3Cscript%3Ealert(1)%3C%2Fscript%3E&token=;script-src-elem%20%27unsafe-inline%27
まとめ
このLABの肝は、「CSPはヘッダ文字列を“セミコロンで区切って”解釈する」という仕様に対し、report-uri の token にセミコロン始まりの文字列を注入し、script-src-elem 'unsafe-inline' を後付けすること。
手順は、反射XSSを確認 → CSPの注入点(token)を発見 → ;script-src-elem 'unsafe-inline'を挿入 → 反射に\を使って実行。 この“ヘッダ合成の脆弱性 × CSPディレクティブの優先関係”の組み合わせは、現場でも起こり得るため、ユーザ入力をHTTPヘッダに混ぜないこと、そしてCSPを厳密に設計することが重要です。
Best regards, (^^ゞ