CSP bypass

少し前に公開されたものだが、PortswiggerのDaily swig経由で見つけた、MSの小勝さんが報告したChromeのCSP bypassバグについて。

やられる側は例えば下記のようなページ。nonceのCSPを使っている。

<meta http-equiv="content-security-policy" content="script-src 'nonce-testrandom'">
<body>
<?= $_GET['param'] ?>    ← エスケープ無し
</body>

攻撃者は以下のようにiframeを挿入してやる(ここでは簡単のためsrcdocを使っている)。

<meta http-equiv="content-security-policy" content="script-src 'nonce-testrandom'">
<body>
<iframe srcdoc='<script>alert(window.origin)</script>'></iframe> ← 攻撃者が挿入したiframeタグ
</body>

攻撃者はiframe内でalertを動かそうとするが、iframeの親ページのCSPがiframeにも継承されるため、正しいnonceが無いJSの実行はブロックされる。

しかし古いChromeでは以下のような方法で回避できた、というのが小勝さんのレポート。

①attackerのページ[A]内のiframeに、victimのページ[V]を入れてやる([V]は上記のiframeが挿入されたCSP付きページ)。

┌─────────────────┐
│attackerのページ[A]        │
│                 │
│┌───────────────┐│
││victimのページ[V]       ││
││               ││
││┌─────────────┐││
│││XSSで挿入したiframe[I]  │││
││└─────────────┘││
│└───────────────┘│
└─────────────────┘
 この時点で[I]には[V]のCSPが適用されている。

②[A]は[I]のURLをdata:text/html,<script>history.back()</script>に操作する。
 この時点で[I]には[A]のCSP(空っぽ)が適用されるようになる。

③[I]のJSが動作してhistory backされる。[I]はXSSで挿入した内容(以下)に戻る。
 <iframe srcdoc='<script>alert(window.origin)</script>'></iframe>

④backした後も、[I]には[A]のCSP(空っぽ)が適用される。
 一方で[I]のオリジンは[V]を継承している。

こんな感じで、CSPのiframeへの継承というメンドクサイところを突いた攻撃です。

修正後は、④にて[I]には[V]のCSPが継承されるようになった模様。Daily swigによると修正にはHTML仕様の追加が必要だったらしい。

他のブラウザではどうかというと、Firefoxは昔から修正後のChromeと同じ挙動だったと思われる。

面白いことに、Safariは挙動が異なっていて③のJS(history.back)は実行されない。これは②で[A]が[I]のURLを変えてもCSPはそのまま[V]のものが適用されるから。それはそれで何となく気持ち悪くはある。