CSRF
一、什么是CSRF
- CSRF(Cross-site request forgery)跨站请求伪造
- 攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的
二、CSRF的攻击类型
-
GET类型的CSRF
- 用户信任
http://bank.com网站,并且保存登录状态 - 诱导用户点击图片
<img src="http://bank.com/transfer?to=attacker&amount=1000"> http://bank.com网站误以为是用户自己发送的请求,并以用户的名义执行了攻击者自定义的操作,触发转账
- 用户信任
-
POST类型的CSRF
-
构造隐藏表单,利用JavaScript自动提交表单到目标网站
<form action="http://bank.example/withdraw" method=POST> <input type="hidden" name="account" value="xiaoming" /> <input type="hidden" name="amount" value="10000" /> <input type="hidden" name="for" value="hacker" /> </form> <script> document.forms[0].submit(); </script>
-
-
链接类型的CSRF
-
在论坛中发布的图片中嵌入**恶意链接,**或者以广告的形式诱导用户中招
<a href="http://test.com/csrf/withdraw.php?amount=1000&for=hacker" taget="_blank"> 重磅消息!! <a/>
-
三、防护策略
-
CSRF的特点与策略
-
特点
- CSRF(通常)发生在第三方域名。
- CSRF攻击者不能获取到Cookie等信息,只是使用。
-
对应策略
- 阻止不明外域的访问
- 同源检测
- Samesite Cookie
- 提交时要求附加本域才能获取的信息
- CSRF Token
- 双重Cookie验证
- 阻止不明外域的访问
-
-
同源检测
-
原理
-
检查请求的
Referer或Origin头是否来自合法域名。 -
注意:部分浏览器可能不发送
Referer(如隐私模式),需结合其他防御手段。// 后端 app.post('/transfer', (req, res) => { const referer = req.get('Referer'); const origin = req.get('Origin'); const allowedDomains = ['https://your-legitimate-site.com']; if (!allowedDomains.includes(referer) && !allowedDomains.includes(origin)) { return res.status(403).send('Invalid request source'); } // 处理合法请求 });
-
-
-
Samesite Cookie
-
Set-Cookie响应头新增Samesite属性,它用来标明这个 Cookie是个“同站 Cookie”,同站Cookie只能作为第一方Cookie,不能作为第三方Cookie。
-
假如淘宝网站用来识别用户登录与否的 Cookie 被设置成了 Samesite=Strict,那么用户从百度搜索页面甚至天猫页面的链接点击进入淘宝后,淘宝都不会是登录状态,因为淘宝的服务器不会接受到那个Cookie,其它网站发起的对淘宝的任意请求都不会带上那个 Cookie。
-
Samesite 有以下属性值
模式 行为 适用场景 Strict 完全禁止跨站请求携带 Cookie(包括用户点击其他站点的链接跳转) 极高敏感操作(如支付、修改密码) Lax 允许安全的跨站 GET 请求(如导航到目标站的链接)携带 Cookie 大多数场景(浏览器默认行为) None 允许所有跨站请求携带 Cookie,但必须同时设置 Secure属性(仅HTTPS)需要跨站 Cookie 的第三方集成场景 // 后端设置Cookie app.post('/api/login', (req, res) => { res.cookie('CSRF-TOKEN', crypto.randomBytes(32).toString('hex'), { secure: true, sameSite: 'Lax', httpOnly: false // 允许前端读取 }); res.send('OK'); }); // 验证 app.post('/api/transfer', (req, res) => { if (req.cookies['CSRF-TOKEN'] !== req.headers['x-csrf-token']) { return res.status(403).send('CSRF验证失败'); } // 处理业务逻辑 });// 前端Axios全局配置自动携带Token axios.interceptors.request.use(config => { const token = document.cookie.match(/CSRF-TOKEN=([^;]+)/)?.[1]; config.headers['X-CSRF-TOKEN'] = token; return config; });
-
-
CSRF Token
-
原理
-
服务器生成一个随机且唯一的 Token,嵌入表单或 HTTP 请求头中,提交时验证 Token 是否合法。
-
Token 需满足:每次会话或请求唯一、加密随机、不可预测。
// 后端 HttpServletRequest req = (HttpServletRequest)request; HttpSession s = req.getSession(); // 从 session 中得到 csrftoken 属性 String sToken = (String)s.getAttribute(“csrftoken”); if(sToken == null){ // 产生新的 token 放入 session 中 sToken = generateToken(); s.setAttribute(“csrftoken”,sToken); chain.doFilter(request, response); } else{ // 从 HTTP 头中取得 csrftoken String xhrToken = req.getHeader(“csrftoken”); // 从请求参数中取得 csrftoken String pToken = req.getParameter(“csrftoken”); if(sToken != null && xhrToken != null && sToken.equals(xhrToken)){ chain.doFilter(request, response); }else if(sToken != null && pToken != null && sToken.equals(pToken)){ chain.doFilter(request, response); }else{ request.getRequestDispatcher(“error.jsp”).forward(request,response); } }<!-- 前端表单嵌入 --!> <!-- 后端渲染页面时注入 Token --> <form action="/transfer" method="POST"> <input type="hidden" name="_csrf" value="{{ csrfToken }}"> <!-- 例如通过模板引擎注入 --> <input type="number" name="amount"> <button>Submit</button> </form>// 前端AJAX请求 // 从 Meta 标签或 Cookie 获取 Token(需后端配合) const csrfToken = document.querySelector('meta[name="csrf-token"]').content; // 发送请求时携带 Token axios('/api/transfer', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken // 通过 HTTP 头传递 }, body: JSON.stringify({ amount: 100 }) });
-
-
-
双重Cookie验证
-
原理
- 无状态验证:无需服务器存储Token,通过客户端自行携带两份Token副本。
- 跨域限制:攻击者无法通过跨域请求同时篡改请求参数和Cookie。
- 动态绑定:Token与用户会话或请求上下文动态绑定,增强安全性。
-
流程
- 生成Token:
- 用户登录时,服务器生成随机Token,写入Cookie(如
CSRF-Token=abc123)。
- 用户登录时,服务器生成随机Token,写入Cookie(如
- 前端传递Token:
- 前端通过JavaScript读取Cookie中的Token,将其添加到请求参数或HTTP头中。
- 服务器验证:
- 服务器比对请求参数/头中的Token与Cookie中的Token是否一致。
- 生成Token:
-
与CSRF的区别
特性 双重Cookie验证 传统CSRF Token 服务端状态管理 无状态 需存储Token(Session/DB/Redis) 实现复杂度 简单,无需存储逻辑 需处理Token生成、存储、验证 抗XSS能力 弱(依赖前端读取Cookie) 强(Token可设置为HttpOnly) 跨域支持 需处理CORS和Cookie跨域策略 需处理Token传递方式 // 后端设置Cookie并验证 // server.js const express = require('express'); const cookieParser = require('cookie-parser'); const cors = require('cors'); const crypto = require('crypto'); const app = express(); app.use(express.json()); app.use(cookieParser()); // 配置CORS(根据实际部署调整) app.use(cors({ origin: 'http://localhost:8080', // Vue前端地址 credentials: true // 允许携带Cookie })); // 登录接口:生成Token并设置Cookie app.post('/api/login', (req, res) => { const { username, password } = req.body; // 模拟用户验证 if (username === 'admin' && password === '123456') { // 生成随机Token const csrfToken = crypto.randomBytes(32).toString('hex'); // 设置Token到Cookie(允许前端读取) res.cookie('CSRF-TOKEN', csrfToken, { httpOnly: false, // 必须允许前端JS读取 secure: process.env.NODE_ENV === 'production', sameSite: 'Lax', // 允许安全跨站请求 maxAge: 86400000 // 24小时有效期 }); return res.json({ success: true }); } res.status(401).json({ error: '认证失败' }); }); // 敏感操作接口:验证双重Cookie app.post('/api/transfer', (req, res) => { const cookieToken = req.cookies['CSRF-TOKEN']; const headerToken = req.headers['x-csrf-token']; if (!cookieToken || cookieToken !== headerToken) { return res.status(403).json({ error: 'CSRF验证失败' }); } // 处理业务逻辑 res.json({ success: true, amount: req.body.amount }); }); app.listen(3000, () => { console.log('Server running on http://localhost:3000'); });// 前端配置Axios全局拦截器 import axios from 'axios'; const instance = axios.create({ baseURL: 'http://localhost:3000/api', withCredentials: true // 允许跨域携带Cookie }); // 请求拦截器:自动添加Token instance.interceptors.request.use(config => { // 从Cookie中获取Token const csrfToken = document.cookie .split('; ') .find(row => row.startsWith('CSRF-TOKEN=')) ?.split('=')[1]; if (csrfToken) { config.headers['X-CSRF-TOKEN'] = csrfToken; } return config; }); export default instance;
-
-
分布式校验
-
原理
- 分布式Token管理:统一生成、存储和验证Token,确保跨服务请求的合法性。
- 动态同步机制:解决多节点间的Token状态同步问题。
- 去中心化验证:减少对单点服务的依赖,提升系统可用性。
-
中央认证服务(推荐)
- 原理:由中心化服务统一生成Token,各服务节点通过API验证Token。
-
使用Session存储CSRF Token会带来很大的压力,使用计算出来的Token,而非随机生成的字符串Token,这样在校验时无需再去读取存储的Token,只用再次计算一次即可(Encrypted Token Pattern方式)
-
-
总结
-
基础防御组合:CSRF Token + SameSite Cookie
-
适用场景:
- 传统多页面Web应用(如Django、Rails应用)
- 混合型架构(部分服务端渲染 + 部分AJAX请求)
-
实现方式:
-
CSRF Token:
- 服务端生成随机Token,嵌入表单或HTTP头(如
X-CSRF-TOKEN)。 - 提交请求时验证Token合法性。
- 服务端生成随机Token,嵌入表单或HTTP头(如
-
SameSite Cookie:
- 设置会话Cookie的
SameSite=Lax,限制跨站请求自动携带Cookie。 - 敏感操作Cookie设为
SameSite=Strict(如支付确认)。
- 设置会话Cookie的
-
-
-
增强防御组合:加密Token + 双重Cookie验证
-
适用场景:
- 单页应用(SPA)(如Vue、React应用)
- 无状态API服务(如RESTful接口)
-
实现方式:
-
加密Token:
- 使用AES或HMAC加密Token,嵌入用户ID、时间戳等元数据。
- 防止Token被窃取后直接重放。
-
双重Cookie验证:
- 服务端设置
CSRF-TOKENCookie(SameSite=None; Secure)。 - 前端读取该Cookie并添加到请求头(如
X-CSRF-Token)。 - 服务端比对Cookie和请求头的Token是否一致。
- 服务端设置
-
-
-
高安全组合:JWT + Origin验证 + SameSite Strict
-
适用场景:
-
高敏感操作(如金融交易、密码修改)
-
跨域API服务(需严格限制来源)
-
-
实现方式:
-
JWT(JSON Web Token):(详情可看前端鉴权之JWT-CSDN博客)
- 用户登录后签发JWT,包含用户身份和操作权限。
- 请求时通过
Authorization: Bearer <token>传递。
-
Origin验证:
- 检查请求头中的
Origin或Referer是否为合法域名。
- 检查请求头中的
-
SameSite Strict:
- 设置关键Cookie为
SameSite=Strict,完全禁止跨站请求携带。
- 设置关键Cookie为
-
-
-