XSS 和 CSRF
在 Web 安全领域,XSS(跨站脚本攻击) 和 CSRF(跨站请求伪造) 是最古老、最常见也是面试中必考的两种攻击方式, 虽然它们名字很像,但它们的攻击目标、原理和防御手段截然不同。
1. XSS (Cross-Site Scripting) 跨站脚本攻击
核心本质:攻击者想方设法将恶意的 JavaScript 代码注入到你的网站页面中,并让受害者的浏览器执行。
攻击目标:客户端(用户的浏览器)。
一句话理解:“你的网站执行了别人的代码。”
1.1 XSS 的三种主要类型
| 类型 | 原理 | 攻击路径示例 |
|---|---|---|
| 存储型 (Stored) | 恶意脚本被永久存储在目标服务器(数据库、文件系统)中。危害最大。 | 1. 攻击者在网站的评论区输入 <script>窃取Cookie代码</script> 并提交。2. 服务器将评论存入数据库。 3. 所有访问该评论页面的用户,浏览器都会执行这段恶意代码。 |
| 反射型 (Reflected) | 恶意脚本作为请求参数(如 URL)发送给服务器,服务器未做处理直接将脚本“反射”回 HTML 页面。 | 1. 攻击者构造恶意链接 http://test.com/search?q=<script>...</script> 并发送给受害者。2. 受害者点击链接。 3. 服务器返回包含恶意脚本的搜索结果页面。 4. 脚本在受害者浏览器执行。 |
| DOM 型 (DOM-based) | 纯粹的前端漏洞。前端 JS 代码在处理 URL 参数、Hash 或其他输入时,未经转义直接操作了 DOM。 | 1. 网址带有恶意参数 http://test.com/#<script>...</script>。2. 页面上的 JS 代码读取了 Hash 值并直接用 innerHTML 插入到页面。3. 脚本执行。整个过程恶意代码不经过服务器。 |
1.2 XSS 的危害
- 窃取 Cookie 和 Session ID:利用
document.cookie获取后发送到攻击者服务器,直接盗用用户账号。 - 页面篡改/钓鱼:修改页面内容,弹出一个假的登录框骗取密码。
- 键盘记录:监听用户的按键操作。
- 发起 CSRF 攻击:利用窃取到的凭证,在后台悄悄执行敏感操作。
1.3 XSS 的防御手段 (防线)
- 防线一:输入过滤/转义 (Sanitization / Escaping)。
- 永远不要相信用户的输入。 任何从客户端提交到服务器,或者从 URL 读取并展示到页面的数据,都必须进行转义(例如将
<转义为<,>转义为>)。可以使用如sanitize-html这样的库。
- 永远不要相信用户的输入。 任何从客户端提交到服务器,或者从 URL 读取并展示到页面的数据,都必须进行转义(例如将
- 防线二:设置
HttpOnlyCookie。- 在服务器端设置 Cookie 时加上
HttpOnly标志。这会让浏览器禁止 JavaScript 通过document.cookie读取该 Cookie。即使 XSS 攻击成功,黑客也偷不到关键的身份令牌。
- 在服务器端设置 Cookie 时加上
- 防线三:内容安全策略 (CSP, Content Security Policy)。
- 通过 HTTP 响应头配置,建立一个白名单,告诉浏览器“只允许加载和执行来自特定域名的脚本”。即使黑客注入了脚本,浏览器也会拒绝执行。
2. CSRF (Cross-Site Request Forgery) 跨站请求伪造
核心本质:攻击者利用受害者在目标网站已登录的身份(浏览器自动携带 Cookie 的机制),诱导受害者在不知情的情况下,对目标网站发送恶意请求。
攻击目标:服务端(利用服务端的信任机制)。
一句话理解:“你代替黑客,向服务器发出了指令。”
2.1 CSRF 攻击的工作流程
- 用户登录了银行网站
bank.com,浏览器保存了包含身份信息的 Cookie。 - 用户在没有登出银行网站的情况下,被诱导访问了黑客的恶意网站
hacker.com。 - 黑客的网站里隐藏了一段代码,例如一个不可见的图片标签:
<img src="http://bank.com/transfer?to=hacker&money=10000" /> - 用户的浏览器会自动解析这个
<img>标签,并向bank.com发起一个 GET 请求。 - 最关键的一步:因为用户之前登录过
bank.com,浏览器在发起这个请求时,会自动、默认地带上bank.com的 Cookie。 - 银行服务器收到请求,验证 Cookie 发现是合法用户,于是执行了转账操作。
2.2 CSRF 攻击成功的三个条件
- 目标网站存在可以通过 API 执行的敏感操作(如转账、修改密码)。
- 受害者刚刚登录过目标网站,且浏览器中的 Cookie 未过期。
- 受害者在未登出的状态下,访问了攻击者精心构造的恶意页面。
2.3 CSRF 的防御手段
- 防线一:同源检测 (Referer Check / Origin Check)。
- 服务器在收到请求时,检查 HTTP 头部的
Referer或Origin字段。如果发现请求是从hacker.com发过来的,而不是从bank.com自家的页面发过来的,就直接拒绝请求。
- 服务器在收到请求时,检查 HTTP 头部的
- 防线二:CSRF Token 验证 (最主流防线)。
- 原理:既然浏览器会自动携带 Cookie,那我们就要求请求中必须携带一个浏览器不会自动发送的东西。
- 做法:用户打开页面时,服务器生成一个随机的 CSRF Token(存放在 HTML 隐藏字段或由 JS 提取)。当用户发起敏感请求(如表单提交、Ajax)时,必须将这个 Token 作为参数或 Request Header 发送给服务器。因为黑客的网站无法跨域读取到这个 Token,伪造的请求就会被服务器拒绝。
- 防线三:Cookie 的
SameSite属性。- 现代浏览器的神器。为 Cookie 设置
SameSite=Strict或Lax。这会告诉浏览器:“只有在当前网页和 Cookie 所属域名完全一致(同站)时,才允许携带此 Cookie;如果是从其他网站(跨站)发起的请求,绝对不允许携带”。这从根本上切断了 CSRF 的命脉。
- 现代浏览器的神器。为 Cookie 设置
3. XSS vs CSRF 深度对比及常见问题 (FAQ)
3.1 核心区别对照表
| 对比维度 | XSS (跨站脚本) | CSRF (跨站请求伪造) |
|---|---|---|
| 攻击方式 | 向页面注入并执行恶意 JS 脚本。 | 借用用户身份,伪造请求发送给服务器。 |
| 攻击目标 | 用户的浏览器。 | 服务器。 |
| 是否需要登录 | 不需要(任何人访问受污染页面都会中招)。 | 必须(依赖受害者已登录的有效 Cookie)。 |
| 对用户凭证的操作 | 试图窃取用户的 Cookie/Token 送给黑客。 | 并不窃取,只是利用浏览器自动带 Cookie 的机制。黑客全程看不到 Cookie 的内容。 |
| 防御核心思路 | 永远不信任用户输入(过滤、转义、限制脚本)。 | 确认请求的来源和意图(Token 校验、SameSite)。 |
3.2 为什么用了 JWT (Token) 就不需要防范 CSRF 了?
- 这是一个常见误区,答案取决于你的 Token 存在哪里。
- 如果你的 JWT 存在 Cookie 中,你依然会受到 CSRF 攻击,因为浏览器还是会自动携带它。
- 只有当你把 JWT 存放在
localStorage/sessionStorage中,并在发请求时使用 JS 手动将其添加到 HTTP Header(如Authorization: Bearer <token>)中时,才天然免疫 CSRF。因为黑客的伪造请求(如<img>或跨域<form>提交)无法触发你的前端 JS 代码去读取 localStorage 并塞入 Header。
3.3 CSRF Token 可以防止 XSS 攻击吗?
- 绝对不能,甚至会被 XSS 秒杀。
- 如果你的网站存在 XSS 漏洞,黑客注入的恶意脚本可以直接读取你页面上的 CSRF Token(无论是写在 DOM 里还是存在全局变量里),然后带着这个合法的 Token 发起 CSRF 请求。
- 结论:XSS 漏洞的危险级别远高于 CSRF,因为一旦发生 XSS,所有的 CSRF 防御措施(除 SameSite 且不允许 JS 读取的 Cookie 外)都将形同虚设。
3.4 设置了 HttpOnly 就能完全防止 XSS 吗?
- 不能完全防止,只能减轻危害。
HttpOnly只能防止通过 JS 读取 Cookie(防盗号)。但 XSS 的危害不仅限于偷 Cookie。- 如果发生了 XSS,黑客依然可以通过执行 JS 脚本来操纵 DOM(例如修改收款账号)、发起 Ajax 请求执行任意操作(此时浏览器仍会自动带上 HttpOnly 的 Cookie 发给服务器)。所以,防范 XSS 的根本还是输入输出的过滤和转义。