
前端开发者必防:CSRF攻击原理与实战防护指南
- 前端开发者必防:CSRF攻击原理与实战防护指南
- 你以为登录就安全了?小心 CSRF 悄悄替你转账
- 从“借刀杀人”理解跨站请求伪造的本质
- 浏览器的“信任陷阱”:同源策略拦不住 CSRF
- 点赞、转账、删账号,一个链接全搞定
- 防御基石:同步令牌(Synchronizer Token)机制详解
- SameSite Cookie:现代浏览器给 CSRF 设下的路障
- 双重提交 Cookie:轻量又有效的防护策略实操
- 验证 HTTP Referer 和 Origin 头:别忽视请求的“来路证明”
- 前后端协作防线:前端埋点 + 后端校验缺一不可
- 常见误区:仅靠验证码或 HTTPS就能防住 CSRF?
- 开发中的真实翻车现场:Token 没绑定用户、动态表单遗漏防护
- 排查思路:如何快速判断一个漏洞是不是 CSRF?
- 调试技巧:用浏览器开发者工具模拟和拦截可疑请求
- 进阶防护组合拳:Token + SameSite + 自定义请求头三重保险
- 隐藏雷区:SPA 应用、第三方嵌入、API 网关下的 CSRF 新挑战
- 让安全成为习惯:在项目脚手架中内置 CSRF 防护模板
- 结语:把 CSRF 想象成“隔壁老王借你钥匙”
前端开发者必防:CSRF攻击原理与实战防护指南
“哥,我就点了个点赞链接,怎么余额没了?”
——某位刚被 CSRF 教育过的同事,在工位上抱着咖啡杯怀疑人生。
如果你以为“登录成功=天下太平”,那就太小看浏览器的“热心肠”了。它会在背后默默帮你带饼干(Cookie),而坏人只需要一张“精心包装”的<img>标签,就能让浏览器把饼干连同你的转账请求一起送到银行——这就是 CSRF(Cross-Site Request Forgery,跨站请求伪造)。今天咱们不背名词,直接拆台:从浏览器为啥这么傻,到前端、后端、运维、测试怎么一起把 CSRF 按在地上摩擦,顺带奉上一车代码,能复制粘贴绝不让你手打。
你以为登录就安全了?小心 CSRF 悄悄替你转账
故事从一个表情包开始。
午休时,你把银行页面开着,Tab 图标安安静静地亮着“已登录”的小绿锁。隔壁组小姐姐发来一张“猫跳机械舞”的 GIF,你顺手点开,页面里除了猫,还有一行肉眼看不见的标签:
<img src="https://bank.example/transfer?to=Attacker&amount=10000">
浏览器一看:哟,这是 bank.example 的域名,我刚好有 Cookie,捎上!
于是请求大摇大摆地过去了,银行后端一验 Cookie:嗯,本人,过!
十秒后,你收到短信:【尊敬的用户,您已转出 10000 元】
你:???我手都没碰键盘!
这就是 CSRF 的骚操作——“借你之手,干我坏事”。它利用的是浏览器“自动带 Cookie”的潜规则,而不是 XSS 那种“先注入脚本再执行”的骚套路。换句话说,CSRF 攻击的请求百分之百来自真实用户,签名、Cookie、Session 样样齐全,就差在脑门上写“我是坏人”。
从“借刀杀人”理解跨站请求伪造的本质
把 CSRF 想成古装剧里的“借刀杀人”:
- 你是御前侍卫(用户),手里有御赐宝刀(Cookie/Session)。
- 坏人(攻击者)造了一把一模一样的刀鞘(请求参数),趁你不注意,把刀插进去(浏览器自动发请求)。
- 守卫(服务器)一看刀是真的,就放行。
- 人头落地,你还一脸懵:我刀怎么自己动了?
核心条件只有三条,记住它,面试能吹五分钟:
- 目标站点仅按 Cookie 识别身份,不看其他“暗号”。
- 用户刚好登录过,Cookie 还没过期。
- 攻击者提前猜到请求参数(转账接口、点赞接口、删除接口……),并能诱导用户点链接/访问页面。
浏览器的“信任陷阱”:同源策略拦不住 CSRF
很多人对“同源策略”有误解,以为它是全能保镖。
其实同源策略只限制读,不限制写。
只要请求是“跨域写”(POST、PUT、DELETE),浏览器就放行,只是不让脚本读响应而已。
CSRF 正好只需要“写”——把钱转走、把文章删了、把密码改了——读不读响应无所谓。
于是同源策略在旁边吃瓜:你们忙,我不管。
点赞、转账、删账号,一个链接全搞定
下面这张表,建议截图当壁纸,下次需求评审直接甩产品脸上:
| 业务场景 | 接口示例 | 是否带 Cookie | 是否幂等 | 是否可被 CSRF |
|---|---|---|---|---|
| 查询余额 | GET /balance | 是 | 是 | 否(只读) |
| 点赞文章 | POST /like?id=1 | 是 | 否 | 是 |
| 修改邮箱 | PUT /email | 是 | 否 | 是 |
| 删除账号 | DELETE /account | 是 | 否 | 是 |
| 上传头像 | POST /avatar(multipart) | 是 | 否 | 是 |
结论:只要请求会“写”数据,且只认 Cookie,就有可能被 CSRF。
别再问“GET 请求会不会被 CSRF”——GET 理论上不该改数据,但架不住有人把“删除”写成 GET,于是悲剧。
防御基石:同步令牌(Synchronizer Token)机制详解
思路一句话
服务器给前端发一张“暗号票”(Token),前端写请求时把票带上,后端验票通过才执行。
因为票是随机且一次性,攻击者猜不到,也读不到(同源策略限制读),于是请求失效。
完整实战:Node(Express) + React
后端:发牌员(生成 Token)
// server/app.js
import express from 'express';
import csurf from 'csurf';
import cookieParser from 'cookie-parser';
const app = express();
app.use(cookieParser());
// 关键中间件:同步令牌
const csrfProtection = csurf({ cookie: true });
// 把令牌塞进接口,让前端自己拿
app.get('/api/csrf-token', csrfProtection, (req, res) => {
res.json({ token: req.csrfToken() }); // 每次返回新 Token
});
// 需要保护的写接口
app.post('/api/transfer', csrfProtection, (req, res) => {
// 只有 Token 校验通过才能到这儿
console.log('转账成功', req.body);
res.json({ ok: 1 });
});
app.listen(3001);
前端:出牌员(把 Token 塞请求头)
// src/utils/request.js 统一封装
import axios from 'axios';
// 先拿 Token,再发业务请求
export async function safePost(url, data) {
// 1. 每次业务请求前,先拿最新 Token
const { data: { token } } = await axios.get('/api/csrf-token');
// 2. 通过自定义头把 Token 带过去,名字任意,前后端对齐即可
return axios.post(url, data, {
headers: { 'x-csrf-token': token },
withCredentials: true // 别忘了 Cookie
});
}
// 使用处
import { safePost } from '@/utils/request';
async function handleLike() {
await safePost('/api/transfer', { to: 'Alice', amount: 1 });
alert('点赞成功');
}
注意细节
- Token 可以存内存,也可以放 Redux、Pinia,千万别放 Cookie——Cookie 会自动带,就白忙活了。
- 对 SPA 来说,首屏渲染前先异步拉 Token,否则用户手速快会 403。
- 表单 SSR 场景,直接把 Token 渲染成
<input type=hidden name=_csrf value=XXX>,后端验name=_csrf字段即可。
SameSite Cookie:现代浏览器给 CSRF 设下的路障
如果你不想折腾 Token,又想让老项目一夜“免疫”80% CSRF,SameSite 是最低成本的大招。
原理一句话
Cookie 加 SameSite=Strict 或 Lax 属性后,浏览器会根据“跨站”情况决定要不要带这枚饼干。
跨站 POST、PUT、DELETE 直接被浏览器拦截,请求都发不出去,还谈什么伪造?
操作示范:一行代码的事
// server/login.js
res.cookie('sessionid', sessId, {
httpOnly: true,
secure: true,
sameSite: 'Strict' // 或 'Lax'
});
| SameSite 取值 | 场景举例 | 是否带 Cookie | 兼容性 |
|---|---|---|---|
| Strict | 任何跨站都不带 | 最严 | 老浏览器不支持 |
| Lax | 顶级导航 GET 可带,POST/iframe 不带 | 折中 | 主流 OK |
| None | 都带(必须同时加 Secure) | 最松 | 所有 |
踩坑提醒
- 用了
SameSite=Strict后,用户从邮件里点开你的链接会“未登录”,因为第一次请求不带 Cookie。 - 如果业务需要被第三方站点 iframe 嵌入,只能
SameSite=None; Secure,同时上 HTTPS。 - 老版本 Chrome(<67)不认识 SameSite,不能当作唯一防线,必须搭配 Token。
双重提交 Cookie:轻量又有效的防护策略实操
“双重提交”是 Token 的穷亲戚:不占用服务端内存,前端把 Cookie 值抄一份放请求头,后端验“两头是否一致”即可。
优点:无状态、易横向扩展;缺点:得自己实现、对 XSS 敏感(Cookie 被读就凉)。
实战:Spring Boot + Vue
后端:发 Cookie,同时验头
@GetMapping("/api/token")
public void genDsc(HttpServletResponse res){
String random = UUID.randomUUID().toString();
// 1. 种 Cookie,httpOnly=false,让前端 js 能读
Cookie c = new Cookie("DSC", random);
c.setPath("/");
c.setHttpOnly(false);
c.setSecure(true);
res.addCookie(c);
// 2. 同时也返到 body,方便前端直接拿
res.getWriter().write("{\"dsc\":\""+random+"\"}");
}
@PostMapping("/api/del")
public Result del(@CookieValue("DSC") String cookieDsc,
@RequestHeader("X-DSC") String headerDsc){
if(!cookieDsc.equals(headerDsc)) throw new RuntimeException("双重提交失败");
return Result.ok();
}
前端:读 Cookie → 放 Header
// Vue3 + axios
import axios from 'axios';
import { getCookie } from '@/utils/cookie'; // 自己封装读 document.cookie
export function delPost(id: number){
// 1. 先拿 DSC Cookie(后端已种下)
const dsc = getCookie('DSC');
return axios.post('/api/del', { id }, {
headers: { 'X-DSC': dsc },
withCredentials: true
});
}
验证 HTTP Referer 和 Origin 头:别忽视请求的“来路证明”
Referer/Origin 是浏览器自带的“来路证明”。
后端直接拒绝“来路不明”的请求,简单粗暴。
但别当唯一防线:
- 企业内网网关、隐私插件会主动剥 Referer,容易误杀;
- 部分老旧浏览器(IE11 某些模式)不发 Origin;
- HTTPS→HTTP 的降级请求Referer 会被浏览器掐掉。
代码示例:Nginx 一层拦截
# 只允许来自自站的 POST
map $http_origin $allow {
~^https://my.com$ 1;
default 0;
}
server {
location /api/ {
if ($request_method = POST) {
set $chk "${allow}0";
if ($chk = "00") { return 403; }
}
proxy_pass http://backend;
}
}
前后端协作防线:前端埋点 + 后端校验缺一不可
很多团队把安全全部推给后端,前端只画按钮,这是耍流氓。
前端能做的不止“拉 Token”:
- 敏感操作二次确认(弹窗、指纹、人脸识别),让“静默伪造”变“用户可感知”。
- 把大接口拆成小接口 + 阶梯权限。比如“提现”拆成“申请提现”+“短信确认”,两次 Token 独立。
- 上线前跑自动化脚本:用 Puppeteer 模拟跨站表单提交,断言返回 403,否则 CI 直接失败。
常见误区:仅靠验证码或 HTTPS就能防住 CSRF?
- 验证码:只能防“重复提交”,不能防跨站第一次提交。攻击者可以先正常访问页面拿到验证码,再构造请求。
- HTTPS:防的是“中间人”,不是“坏人借你的身份”。HTTPS 甚至让 CSRF 更难被网络层发现,因为流量被加密。
- CORS 配置正确就不会 CSRF?CORS 控制的是“读”响应,CSRF 是“写”请求,两码事。
开发中的真实翻车现场:Token 没绑定用户、动态表单遗漏防护
翻车 1:Token 全局复用,没和用户绑定
// 错误示范:Token 存在全局变量,A 用户登录后 B 用户直接用
app.use((req, res, next) => {
req.csrfToken = globalToken; // ❌ 所有人共用
});
正确姿势:csurf 中间件默认把 Token 存在 Session,Session 与用户绑定,别手贱改。
翻车 2:动态表单没更新 Token
// 错误示范:React 路由切换时没重新拉 Token
const [token, setToken] = useState(window.TOKEN_FROM_HTML);
function addRow(){
// 用户 F5 后 Token 是新值,但 SPA 内跳转没刷新,导致 403
}
修复:路由切换后重新 GET /api/csrf-token,或把 Token 过期时间设短 + 自动刷新。
排查思路:如何快速判断一个漏洞是不是 CSRF?
- 打开 Burp → 右键
Engagement tools → Generate CSRF PoC→ 生成 HTML。 - 换浏览器(或无痕)登录受害账号,保持 Cookie 有效。
- 用 Burp 自带的浏览器打开 PoC,如果请求 200 并且数据被改,基本坐实。
- 看后端日志:有没有自定义头/Token?Referer 是不是外站?SameSite 值?
- 如果接口返回
400 bad csrf token或403 missing x-csrf-token,说明已有防护,但前端调用姿势不对。
调试技巧:用浏览器开发者工具模拟和拦截可疑请求
- Network 面板 → 右键请求 → Copy as fetch,改完 Origin 再回车,看后端是否拦截。
- Sources → Overrides,把页面里 Token 改成 12345,刷新后看是否 403。
- Application → Cookies,双击改 SameSite 值,实时验证不同策略。
- Chrome 94+ 支持“CSRF 保护演示”:地址栏打开
chrome://flags/#network-service-csrf-protection,开启后可看浏览器级别的拦截日志。
进阶防护组合拳:Token + SameSite + 自定义请求头三重保险
单点防御总有“漏网之鱼”,小孩才做选择,大人全都要。
推荐“三明治”配置:
- SameSite=Lax:挡掉 80% 广告条、邮件里的跨站 POST。
- 自定义头 + Token:
- 所有 JSON 请求统一带
X-Requested-With: XMLHttpRequest(axios 默认)。 - 后端拒绝一切没有该头的 POST,因为浏览器
<form>、<img>无法自定义头,天然防住最原始的 CSRF。
- 所有 JSON 请求统一带
- 双重提交 Cookie:给无状态服务用,横向扩展无压力。
// axios 拦截器统一加标识
axios.interceptors.request.use(config => {
config.headers['X-Requested-With'] = 'XMLHttpRequest';
return config;
});
隐藏雷区:SPA 应用、第三方嵌入、API 网关下的 CSRF 新挑战
SPA:路由跳转不刷新,Token 怎么续?
- 方案 A:Token 过期 15 min,每次路由跳转异步刷新。
- 方案 B:GraphQL 统一走
/graphql,在 HTTP 头里带 Token,与 REST 互不干扰。
第三方嵌入:你的页面被 iframe 到 evil.com
- 后端加
X-Frame-Options: SAMEORIGIN或 CSPframe-ancestors 'self'。 - 如果必须允许嵌入,只能 SameSite=None; Secure + Token 双重保险,同时把敏感操作再包一层短信验证码。
API 网关:Spring Cloud Gateway 转发请求,Token 存在哪?
- 网关层不负责验 Token,只负责把 Cookie 里的 SessionID 转发到用户服务。
- 验 Token 放到下游微服务,防止“网关改代码,业务无感知”导致漏洞。
让安全成为习惯:在项目脚手架中内置 CSRF 防护模板
别再指望“上线前渗透测试”给你擦屁股,安全左移才是正道。
把下面这套模板写进公司 Yeoman/CLI 仓库,新项目一键生成,谁不用的 MR 直接打回:
├── template
│ ├── _package.json // 依赖 csurf / spring-security
│ ├── src
│ │ ├── utils
│ │ │ └── safeRequest.js // 自带 Token 自动续期
│ │ └── hooks
│ │ └── useCsrf.tsx // React Hook,页面挂载即拉 Token
│ └── nginx.conf // 已配好 Referer 白名单
CI 再加一条:
# .gitlab-ci.yml
csrf-test:
script:
- npm run test:csrf # 使用 OWASP ZAP 脚本,跑 5 分钟,失败就红 pipe
结语:把 CSRF 想象成“隔壁老王借你钥匙”
浏览器很热心,谁敲门都递钥匙;
服务器很憨厚,见钥匙就开门;
攻击者最狡猾,钥匙不用偷,只要让你“亲手”递过去。
前端、后端、运维、测试,人人都是保安。
下次需求评审,把这篇文章甩出去,让产品经理知道:加一个“确认”按钮,不只是体验,更是安全。
毕竟,代码可以复制粘贴,余额却不能。
欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
推荐:DTcode7的博客首页。
一个做过前端开发的产品经理,经历过睿智产品的折磨导致脱发之后,励志要翻身农奴把歌唱,一边打入敌人内部一边持续提升自己,为我们广大开发同胞谋福祉,坚决抵制睿智产品折磨我们码农兄弟!
| 专栏系列(点击解锁) | 学习路线(点击解锁) | 知识定位 |
|---|---|---|
| 《微信小程序相关博客》 | 持续更新中~ | 结合微信官方原生框架、uniapp等小程序框架,记录请求、封装、tabbar、UI组件的学习记录和使用技巧等 |
| 《AIGC相关博客》 | 持续更新中~ | AIGC、AI生产力工具的介绍,例如stable diffusion这种的AI绘画工具安装、使用、技巧等总结 |
| 《HTML网站开发相关》 | 《前端基础入门三大核心之html相关博客》 | 前端基础入门三大核心之html板块的内容,入坑前端或者辅助学习的必看知识 |
| 《前端基础入门三大核心之JS相关博客》 | 前端JS是JavaScript语言在网页开发中的应用,负责实现交互效果和动态内容。它与HTML和CSS并称前端三剑客,共同构建用户界面。 通过操作DOM元素、响应事件、发起网络请求等,JS使页面能够响应用户行为,实现数据动态展示和页面流畅跳转,是现代Web开发的核心 | |
| 《前端基础入门三大核心之CSS相关博客》 | 介绍前端开发中遇到的CSS疑问和各种奇妙的CSS语法,同时收集精美的CSS效果代码,用来丰富你的web网页 | |
| 《canvas绘图相关博客》 | Canvas是HTML5中用于绘制图形的元素,通过JavaScript及其提供的绘图API,开发者可以在网页上绘制出各种复杂的图形、动画和图像效果。Canvas提供了高度的灵活性和控制力,使得前端绘图技术更加丰富和多样化 | |
| 《Vue实战相关博客》 | 持续更新中~ | 详细总结了常用UI库elementUI的使用技巧以及Vue的学习之旅 |
| 《python相关博客》 | 持续更新中~ | Python,简洁易学的编程语言,强大到足以应对各种应用场景,是编程新手的理想选择,也是专业人士的得力工具 |
| 《sql数据库相关博客》 | 持续更新中~ | SQL数据库:高效管理数据的利器,学会SQL,轻松驾驭结构化数据,解锁数据分析与挖掘的无限可能 |
| 《算法系列相关博客》 | 持续更新中~ | 算法与数据结构学习总结,通过JS来编写处理复杂有趣的算法问题,提升你的技术思维 |
| 《IT信息技术相关博客》 | 持续更新中~ | 作为信息化人员所需要掌握的底层技术,涉及软件开发、网络建设、系统维护等领域的知识 |
| 《信息化人员基础技能知识相关博客》 | 无论你是开发、产品、实施、经理,只要是从事信息化相关行业的人员,都应该掌握这些信息化的基础知识,可以不精通但是一定要了解,避免日常工作中贻笑大方 | |
| 《信息化技能面试宝典相关博客》 | 涉及信息化相关工作基础知识和面试技巧,提升自我能力与面试通过率,扩展知识面 | |
| 《前端开发习惯与小技巧相关博客》 | 持续更新中~ | 罗列常用的开发工具使用技巧,如 Vscode快捷键操作、Git、CMD、游览器控制台等 |
| 《photoshop相关博客》 | 持续更新中~ | 基础的PS学习记录,含括PPI与DPI、物理像素dp、逻辑像素dip、矢量图和位图以及帧动画等的学习总结 |
| 日常开发&办公&生产【实用工具】分享相关博客》 | 持续更新中~ | 分享介绍各种开发中、工作中、个人生产以及学习上的工具,丰富阅历,给大家提供处理事情的更多角度,学习了解更多的便利工具,如Fiddler抓包、办公快捷键、虚拟机VMware等工具 |
吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤
非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!


1101

被折叠的 条评论
为什么被折叠?



