第二章:XSS攻击原理——反射型、存储型、DOM型XSS的完整攻击链路
大家好,我是你们的前端安全讲师。今天我们来聊聊XSS攻击。
说实话,XSS(跨站脚本攻击)是我在项目中遇到最多的安全问题。没有之一。我记得刚入行那会儿,总觉得XSS离自己很远,直到有一次线上系统被注入了恶意脚本,用户数据差点泄露……嗯,从那以后,我再也不敢轻视它了。
XSS说白了就是攻击者把恶意脚本塞到你的网页里,然后让浏览器执行。为什么会这样?因为浏览器太“信任”用户输入了。你想想看,它分不清哪些是正常代码,哪些是恶意代码。
XSS主要分三种:反射型、存储型、DOM型。咱们一个一个来看。
一、反射型XSS(非持久型)
反射型XSS,也叫非持久型XSS。它的特点是:恶意脚本不会存到服务器上,而是通过URL参数“反射”回来。
攻击链路是这样的:
- 攻击者构造一个恶意链接,比如:
https://example.com/search?q=<script>alert('xss')</script> - 用户点击了这个链接
- 服务器收到请求,把参数
q的值直接拼到了HTML里返回 - 浏览器解析HTML,执行了
<script>alert('xss')</script> - 攻击成功
关键点:反射型XSS需要用户主动点击恶意链接。攻击者通常会通过钓鱼邮件、短信、社交平台来传播这个链接。
我在项目中遇到过这样一个案例:一个搜索框,用户输入关键词后,页面会显示“您搜索的是:xxx”。后端直接把关键词拼到了HTML里,没有任何过滤。结果呢?攻击者构造了一个链接,里面带了恶意脚本,用户一点击,cookie就被偷走了。
避坑指南:我曾经以为只要对<script>标签做过滤就够了。后来发现攻击者可以用<img onerror>、<svg onload>等方式绕过。所以,永远不要信任用户输入。
二、存储型XSS(持久型)
存储型XSS,也叫持久型XSS。这是最危险的一种。为什么?因为恶意脚本被存到了服务器上,所有访问这个页面的用户都会中招。
攻击链路是这样的:
- 攻击者在评论区、留言板、个人简介等地方提交恶意代码
- 服务器没有做过滤,直接把恶意代码存到了数据库里
- 其他用户访问这个页面时,服务器从数据库取出恶意代码,拼到HTML里返回
- 浏览器执行恶意脚本
- 所有访问者都成了受害者
注意:存储型XSS的危害范围最大。一个漏洞可能影响成千上万的用户。我见过一个论坛因为存储型XSS,导致管理员账号被盗,整个站点被篡改。
我记得有个经典案例:某社交平台允许用户在个人签名里写HTML代码。攻击者写了一段脚本,自动关注自己的账号。结果呢?所有看过他个人主页的用户,都莫名其妙关注了他。这就是存储型XSS的威力。
存储型XSS和反射型XSS的区别在于:
| 对比项 | 反射型XSS | 存储型XSS |
|---|---|---|
| 恶意代码存储位置 | URL中 | 服务器数据库 |
| 触发方式 | 用户点击恶意链接 | 用户访问正常页面 |
| 危害范围 | 单个用户 | 所有访问者 |
| 检测难度 | 较低 | 较高 |
三、DOM型XSS
DOM型XSS跟前两种不太一样。它不经过服务器,完全在客户端发生。说白了,就是JavaScript操作DOM时,把恶意数据当成了代码执行。
攻击链路是这样的:
- 攻击者构造恶意链接,比如URL的hash或search参数里带了恶意代码
- 用户点击链接,页面加载
- 前端JavaScript代码从URL中取出参数,直接通过
innerHTML、document.write等方式插入到DOM中 - 浏览器执行了恶意脚本
- 攻击成功
关键点:DOM型XSS的恶意代码不经过服务器。服务器返回的HTML是正常的,但客户端的JS代码“创造”了漏洞。
我举个例子:
// 危险代码
var name = new URLSearchParams(window.location.search).get('name');
document.getElementById('welcome').innerHTML = '欢迎您,' + name;
// 如果URL是:https://example.com?name=<img src=x onerror=alert(1)>
// 那么恶意代码就会被执行
为什么会这样?因为innerHTML会把字符串当作HTML解析。攻击者只要在参数里塞一个<img onerror>或者<svg onload>,就能执行任意脚本。
避坑指南:我曾经在项目里用innerHTML渲染用户评论,觉得只要后端过滤了就行。后来发现DOM型XSS根本不需要后端参与。所以,前端也要做过滤。用textContent代替innerHTML,或者用DOMPurify库做净化。
三种XSS的对比总结
| 类型 | 存储位置 | 触发方式 | 是否需要服务器参与 | 危害等级 |
|---|---|---|---|---|
| 反射型 | URL | 点击恶意链接 | 是 | 中 |
| 存储型 | 数据库 | 访问正常页面 | 是 | 高 |
| DOM型 | URL/本地存储 | 点击恶意链接 | 否 | 中高 |
嗯,到这里三种XSS的攻击链路就讲完了。你可能会问:怎么防御?别急,后面几章我会专门讲防御方案。但今天你得先记住一个核心原则:永远不要信任用户输入,永远不要信任URL参数,永远不要用innerHTML直接插入用户数据。
我个人习惯在写代码时,只要遇到innerHTML、document.write、eval、setTimeout字符串参数这些API,就会停下来想一想:这里的数据来源可靠吗?会不会被攻击者控制?
好了,这一章就到这里。下一章我们聊聊XSS的实战防御方案,包括CSP、输入过滤、输出编码这些干货。到时候见。