第四章 Web漏洞之XSS:反射型、存储型、DOM型、绕过技巧与CSP策略
XSS,全称跨站脚本攻击。说白了,就是攻击者把恶意脚本塞进了你的网页里。用户一访问,脚本就在浏览器里跑起来了。我见过太多开发人员觉得这漏洞“不就是弹个窗嘛”,结果被拿走了Cookie、劫持了会话,甚至被用来做钓鱼页面。
这一章,咱们把XSS的几种形态、常见的绕过手法,以及怎么用CSP做最后一道防线,一次性讲透。
4.1 反射型XSS:最基础的“一次性”攻击
反射型XSS,恶意脚本藏在URL参数里。服务端没过滤,直接把它塞回响应页面。用户点开链接,脚本就执行了。它不持久,就这一次。
核心特征:恶意脚本通过当前HTTP请求的URL参数传入,服务端立即反射回响应体中。
举个例子,一个搜索页面:
// 漏洞代码示例
String query = request.getParameter("q");
out.println("<h1>您搜索的内容是:" + query + "</h1>");
攻击者构造这样的URL:
https://example.com/search?q=<script>alert(document.cookie)</script>
用户一点,脚本就执行了。我早年做渗透测试时,就靠这个拿过不少测试站点的管理员Cookie。嗯,这里要注意:反射型XSS通常需要诱导用户点击,所以常跟钓鱼邮件、短链接配合使用。
4.2 存储型XSS:真正的大杀器
存储型XSS,恶意脚本被永久保存在服务端——数据库、文件系统、缓存里都行。每个访问受影响页面的用户,都会中招。这才是真正让人头疼的。
危险等级:高。存储型XSS不需要用户点击特定链接,只要访问正常页面就会触发。论坛、评论区、用户资料编辑页是重灾区。
我在项目中遇到过这样一个案例:一个企业内部的知识库系统,用户可以在文章下面评论。评论内容直接入库,展示时也没做转义。攻击者提交了包含恶意脚本的评论,之后每个查看该文章的员工都中招了。Cookie被窃取,内部系统被横向渗透。那次排查花了我整整两天。
典型的存储型XSS流程:
- 攻击者提交恶意数据(评论、留言、个人签名等)
- 服务端未做过滤,直接存入数据库
- 其他用户访问页面,服务端从数据库取出数据
- 未转义直接输出到HTML,脚本执行
4.3 DOM型XSS:纯前端的“暗箭”
DOM型XSS跟前两种不一样。它不经过服务端,完全是前端JavaScript在操作DOM时,把不可信数据写入了HTML。服务端响应里根本看不到恶意脚本的影子。
你想想看,服务端返回的页面是干净的,但浏览器端的JS代码把URL中的参数取出来,直接塞进了innerHTML。这就出事了。
// 漏洞代码示例
var name = new URLSearchParams(window.location.search).get('name');
document.getElementById('welcome').innerHTML = '欢迎您,' + name;
攻击者构造:
https://example.com/welcome?name=<img src=x onerror=alert(1)>
服务端返回的页面里没有<script>标签,但JS执行后,DOM被修改了。这就是DOM型XSS的狡猾之处——WAF和服务器端过滤器很难拦住它。
排查技巧:检查所有使用 innerHTML、outerHTML、document.write、eval、setTimeout(字符串形式)的地方。这些API是DOM型XSS的高发区。
4.4 绕过技巧:攻击者不会按套路出牌
你以为加了过滤就安全了?太天真了。攻击者的绕过手法层出不穷。我整理了几个最常见的,你排查时一定要重点看。
| 绕过类型 | 原始过滤 | 绕过示例 |
|---|---|---|
| 大小写混合 | 过滤 <script> | <ScRiPt>alert(1)</ScRiPt> |
| 双写绕过 | 删除 script 标签 | <scrscriptipt>alert(1)</scrscriptipt> |
| 事件处理器 | 过滤 <script> | <img src=x onerror=alert(1)> |
| 编码绕过 | 过滤尖括号 | <img src=x onerror=alert(1)> |
| 伪协议 | 过滤 http:// | <a href="javascript:alert(1)">点我</a> |
我曾经遇到一个站点,它只过滤了 <script> 标签。攻击者直接用 <img onerror> 就绕过去了。所以,别只盯着 script 标签,事件处理器、伪协议、SVG、style 标签都是攻击面。
4.5 CSP策略配置:最后一道防线
CSP,内容安全策略。它是个好东西。就算你的代码里还有XSS漏洞,CSP也能限制攻击者能干什么。说白了,它就是告诉浏览器:哪些来源的脚本可以执行,哪些不行。
CSP通过HTTP响应头来配置:
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com; object-src 'none';
这条策略的意思是:
- 所有资源默认只允许从同源加载
- 脚本可以从同源和 trusted.cdn.com 加载
- 不允许使用 <object>、<embed>、<applet> 标签
注意:不要使用 'unsafe-inline' 和 'unsafe-eval'。这两个一开,CSP基本等于白设。我见过太多人图省事直接加上,结果CSP形同虚设。
配置CSP的几个关键点:
- 从严格模式开始:先用 default-src 'none',再逐步放行需要的资源
- 使用nonce或hash:对于内联脚本,用 nonce 或 hash 来白名单化
- 开启报告模式:先用 Content-Security-Policy-Report-Only 收集违规报告,再正式启用
- 定期审查:业务在变,CSP也要跟着调整
下面这张图,帮你理清三种XSS的攻击路径和CSP的防御位置:
4.6 实战排查清单
排查XSS漏洞,我有一套固定的流程。分享给你:
- 找入口:扫描所有用户输入点——URL参数、表单、HTTP头、文件上传
- 追出口:检查这些输入最终出现在哪些HTML上下文中——标签内、属性值、事件处理器、CSS、JS代码
- 看编码:确认服务端是否做了上下文感知的编码——HTML实体编码、JS编码、URL编码
- 测绕过:用常见的绕过payload测试过滤逻辑是否完整
- 配CSP:配置并启用CSP,用报告模式收集违规情况
我的习惯:在代码审计时,优先搜索 innerHTML、outerHTML、document.write、eval、setTimeout、setInterval 这些高危API。找到它们,再反向追踪数据来源。这比正向梳理输入输出快得多。