4、跨站脚本攻击(XSS)防御:XSS类型(反射型/存储型/DOM型)、内容安全策略(CSP)、HttpOnly Cookie
跨站脚本攻击,简称 XSS。说实话,这是 Web 安全里最常见、也最容易被忽视的问题。我见过太多团队,功能做得花里胡哨,结果一个 XSS 就把用户数据全漏了。
简单说,XSS 就是攻击者往你的页面里塞了一段恶意脚本。浏览器傻乎乎地执行了,然后 cookie、token、用户隐私全被偷走。嗯,咱们今天就把这事彻底讲清楚。
4.1 三种 XSS 类型,你都得认识
XSS 分三种:反射型、存储型、DOM 型。我一个个说。
4.1.1 反射型 XSS
这种最直接。攻击者把恶意脚本放在 URL 参数里,用户一点链接,服务器就把脚本“反射”回页面执行。
举个例子:
// 不安全的代码
app.get('/search', (req, res) => {
let keyword = req.query.q;
res.send('<h1>搜索结果:' + keyword + '</h1>');
});
如果用户访问 /search?q=<script>alert('xss')</script>,脚本就执行了。
我在项目中遇到过,有人把搜索框的输入直接拼到 HTML 里返回。我当时就问他:“你想想看,用户搜个 <script> 会怎样?”他脸都白了。
4.1.2 存储型 XSS
这个更危险。恶意脚本被存到数据库里,所有访问页面的用户都会中招。
典型的场景是评论区:
// 用户提交评论
POST /comment
{
"content": "<script>stealCookie()</script>"
}
// 展示评论时直接输出
<div>{{ comment.content }}</div>
只要这条评论没被转义,每个看评论的人都会执行 stealCookie()。我见过一个论坛因此泄露了上万用户的登录态。
4.1.3 DOM 型 XSS
这种 XSS 完全在浏览器端发生。服务器不参与,纯前端代码把恶意内容写进了 DOM。
比如:
// 从 URL 获取参数
let name = new URLSearchParams(window.location.search).get('name');
// 直接插入 DOM
document.getElementById('greeting').innerHTML = '你好,' + name;
如果 URL 里 ?name=<img src=x onerror=alert(1)>,那页面就炸了。
我个人习惯,所有从 window.location、document.URL、localStorage 获取的数据,都当成不可信输入处理。你永远不知道用户会传什么进来。
4.2 防御 XSS 的核心手段
说完了攻击,咱们聊聊怎么防。我总结了三个关键点。
4.2.1 输出编码与转义
这是最基础的。所有用户输入,在输出到 HTML 之前,必须转义。
常见的转义规则:
| 原始字符 | 转义后 |
|---|---|
| < | < |
| > | > |
| " | " |
| ' | ' |
| & | & |
在 Java 里用 StringEscapeUtils.escapeHtml4(),在 Python 里用 html.escape(),在 JavaScript 里用 textContent 代替 innerHTML。
v-html 不会,要慎用。
4.2.2 HttpOnly Cookie
这个我特别想强调。很多 XSS 攻击的目标就是偷 cookie。你只要给 cookie 加上 HttpOnly 标记,JavaScript 就读取不到它。
设置方式:
// Java
response.setHeader("Set-Cookie", "sessionId=abc123; HttpOnly; Secure");
// Node.js (Express)
res.cookie('sessionId', 'abc123', { httpOnly: true, secure: true });
// Python (Django)
response.set_cookie('sessionId', 'abc123', httponly=True, secure=True)
我曾经见过一个项目,所有 cookie 都没设 HttpOnly。攻击者一个 XSS 就把所有人的 session 全偷走了。你说冤不冤?
4.2.3 内容安全策略(CSP)
CSP 是个好东西。它告诉浏览器:哪些来源的脚本可以执行,哪些不行。就算攻击者注入了 <script> 标签,浏览器也会直接忽略。
一个典型的 CSP 头:
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com; object-src 'none'
这个策略的意思是:
- 所有资源默认只允许从本站加载
- 脚本可以从本站和
trusted.cdn.com加载 - 禁止
<object>、<embed>等插件
我建议你从 Content-Security-Policy-Report-Only 模式开始。它不会真的拦截,只会报告违规情况。等确认没问题了,再切换到正式模式。
4.3 综合防御策略
说实话,没有银弹。XSS 防御需要多层配合。
我总结了一个三层防御模型:
- 输入层:对所有用户输入做校验和过滤。但别完全依赖它,因为总有绕过方式。
- 输出层:所有输出到 HTML 的内容必须转义。这是底线。
- 策略层:用 CSP 和 HttpOnly 做兜底。就算前两层失效,攻击者也拿不到 cookie,脚本也执行不了。
你想想看,三层都做好,攻击者得多费劲才能突破?
4.4 常见误区
我见过不少团队在防 XSS 时犯的错,列出来给你提个醒:
- 只过滤不转义:过滤能挡住大部分,但总有漏网之鱼。转义才是最后一道防线。
- 只在后端做防御:DOM 型 XSS 纯前端发生,后端根本不知道。前端也得防。
- 用了 CSP 就万事大吉:CSP 有兼容性问题,而且配置不当很容易被绕过。它是个好工具,但不是万能药。
- 忽略 JSONP 和 eval:这些也是 XSS 的常见入口。能用
JSON.parse就别用eval。
innerHTML、document.write、eval 的使用。这三个函数,能不用就不用。
好了,XSS 防御就讲到这里。记住一句话:永远不要信任用户的输入。哪怕是你自己写的测试数据,也要当成恶意数据来处理。这是安全编码的第一条铁律。