以下の内容はhttps://cysec148.hatenablog.com/entry/2025/08/19/173058より取得しました。


【有料試作版】PortSwigger LAB解説:Reflected XSS protected by CSP, with CSP bypass(CSPを“ヘッダ注入”でねじ曲げてXSS)

Hello there, ('ω')ノ

ねらい

このLABは、検索結果に反射型XSSがある一方で、レスポンスヘッダのCSP(Content-Security-Policy)によって実行が止められます。ところがCSPの中にあるreport-uri の token パラメータがユーザ入力に依存しており、ここからCSPディレクティブを追記注入できます。

狙いは、token に「;script-src-elem 'unsafe-inline'」を差し込んでポリシーを上書きし、インラインスクリプトを許可させて alert(1) を出すこと。

※ この解法はChrome前提です(LABの注記どおり)。


全体像(まずはストーリー)

  1. 検索欄に <img src=1 onerror=alert(1)> を入れて反射を確認 → CSPでブロック
  2. BurpでレスポンスヘッダContent-Security-Policyを見ると、report-uritoken=…が付与されており、この token がURLのクエリから取り込まれていると分かる。
  3. “;”(セミコロン)はCSPでディレクティブ区切り。よって token に ;script-src-elem 'unsafe-inline' を注入すれば、新しいディレクティブを丸ごと追加できる。
  4. script-src-elemscript要素に限定して適用され、既存の script-src より優先される(Chrome実装に依存)。
  5. 最後に検索語へ<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=... のような記述。かつtokenURLクエリから未エスケープで合成されている。
  • なぜ:この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-elemscriptタグに対するポリシーを個別に制御し、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-elemscript-src より粒度が細かく、適用対象が重なる場合は後者を上書きする(Chromeの実装準拠)。
  • 結果、'unsafe-inline' が有効化され、インラインscriptが動く。

実務目線の防御

  • ヘッダにユーザ入力を混ぜない:どうしても必要なら厳格なエンコード(セミコロン/改行/クォート禁止)。
  • CSPは厳格に

    • nonce もしくは hash('sha256-…')インライン禁止を徹底。
    • script-src-elemscript-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, (^^ゞ




以上の内容はhttps://cysec148.hatenablog.com/entry/2025/08/19/173058より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14