なに

CSP Embedded Enforcementを使って、JavaScriptで書かれたXSS mitigationをbypassできるパターンがあるという話。
以下のような条件が必要なので現実に使える場面は普通はなさそう。
CTFでは便利かもしれない。

  • Webページで同一オリジンのページをiframeで読み込める
  • WebページのヘッダにCSPが設定されていない
  • Webページに指定したHTMLを埋め込める(XSSができる)
  • ただし、WebページにJavaScriptによるXSS防御機構がある

このとき、CSP Embedded Enforcementを使うことで、防御機構を回避して攻撃コードだけを実行することができる。

<iframe src="VULNERABLE_PAGE?xss=<script nonce=12345>alert(document.cookie)</script>" csp="script-src 'nonce-12345';">

h4x0rs.date

と、言うのが0CTF 2018 finalsで出題されたh4x0rs.dateという問題で使えたテクニックでした。

問題の解説はここにあるけど、XSSの可能なページが以下のJavaScript(csp.js)によって保護されているというものです。

meta = document.createElement('meta');
meta.httpEquiv='Content-Security-Policy';
meta.content="script-src 'nonce-77aae8705604c721a0c4bb7b9e3088976c2b44a1bb74c7624b37ed853cdf5b4f_profilephp_7f666d4acbd2c9163a5c4aad1747c7fc'";
document.head.appendChild(meta);

このcsp.jsがhead内で読み込まれCSPが設定されることにより、攻撃コードの実行が阻止される。(nonceは読み込むたびに変化する)
想定解としては、csp.jsに Cache-Control: max-age=20 がついているので、うまくnonceを漏洩させてキャッシュの有効なうちにXSSするというもの。

こういう問題設定だとCSP Embedded Enforcementを使って簡単に解けるということに気づきました。
profile.phpに以下のようなiframeを埋め込めば、csp.jsの読み込みが阻止され、攻撃コードを実行できます。
便利ですね。

<iframe src="/profile.php?id=id_to_xssed_page" csp="script-src 'unsafe-inline';">

CSP Embedded Enforcement

この解き方を見つけた時はCSP Embedded Enforcementを知らなかったんですけど、iframeで埋め込む側がCSPを提案するという仕様らしいですね。へー。

cross originだとリクエスト時のSec-Required-CSPヘッダで提案するだけなんですが、same originだと勝手にCSPがつくみたいです。

同じページでもstyleのない状態にするとかできます。

document.body.innerHTML = removeXSS(document.body.innerHTML) みたいなのがbodyの先頭で呼ばれているとかだと回避できるかも。

もうちょい面白い使い方があればいいんですが、パッと思いつかないですね。
覚えておけばそのうち使えるかもしれない?