第四章 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流程:

  1. 攻击者提交恶意数据(评论、留言、个人签名等)
  2. 服务端未做过滤,直接存入数据库
  3. 其他用户访问页面,服务端从数据库取出数据
  4. 未转义直接输出到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)>
编码绕过 过滤尖括号 &lt;img src=x onerror=alert(1)&gt;
伪协议 过滤 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的几个关键点:

  1. 从严格模式开始:先用 default-src 'none',再逐步放行需要的资源
  2. 使用nonce或hash:对于内联脚本,用 nonce 或 hash 来白名单化
  3. 开启报告模式:先用 Content-Security-Policy-Report-Only 收集违规报告,再正式启用
  4. 定期审查:业务在变,CSP也要跟着调整

下面这张图,帮你理清三种XSS的攻击路径和CSP的防御位置:

XSS攻击路径与CSP防御示意图 XSS攻击路径与CSP防御位置 攻击者 ① 反射型:URL参数 ② 存储型:数据库 ③ DOM型:前端JS Web服务端 数据库 浏览器(受害者) CSP防御:浏览器端执行策略,阻止恶意脚本加载与执行 说明:CSP是最后一道防线,不能替代输入过滤和输出转义,但能极大降低漏洞利用效果

4.6 实战排查清单

排查XSS漏洞,我有一套固定的流程。分享给你:

  1. 找入口:扫描所有用户输入点——URL参数、表单、HTTP头、文件上传
  2. 追出口:检查这些输入最终出现在哪些HTML上下文中——标签内、属性值、事件处理器、CSS、JS代码
  3. 看编码:确认服务端是否做了上下文感知的编码——HTML实体编码、JS编码、URL编码
  4. 测绕过:用常见的绕过payload测试过滤逻辑是否完整
  5. 配CSP:配置并启用CSP,用报告模式收集违规情况

我的习惯:在代码审计时,优先搜索 innerHTML、outerHTML、document.write、eval、setTimeout、setInterval 这些高危API。找到它们,再反向追踪数据来源。这比正向梳理输入输出快得多。