SQL注入和XSS攻击的是服务端/客户端代码 ,而CSRF(跨站请求伪造)攻击的是用户的浏览器身份认证机制。
它们是Web安全领域的"三驾马车",但CSRF的设计机理和前两者完全不同------它不需要窃取数据 ,而是借用你的身份做坏事。
下面我从设计本质 、攻击链路 和底层协议缺陷三个维度,为你彻底拆解CSRF。
第一部分:设计本质 ------ "信任的滥用"
CSRF的核心根因是:Web应用无法区分"用户本人发起的请求"和"用户不知情下由恶意网站发起的请求"。
经典隐喻:
-
你(用户)登录了银行网站,浏览器保存了你的身份凭证(Cookie)。
-
你打开了一个恶意网页,该网页偷偷向银行网站发起转账请求。
-
银行网站看到请求中带着你的Cookie,以为是你本人操作,于是执行了转账。
关键点 :CSRF不是通过窃取Cookie来冒充你(它根本拿不到你的Cookie),而是利用浏览器自动携带Cookie的机制,让银行网站"误以为"请求是你主动发起的。
第二部分:底层工作原理 ------ 攻击的"时空错位"
要理解CSRF,必须深入理解HTTP协议的无状态性 和浏览器的Cookie策略。
1. 身份凭证的"自动携带"机制(CSRF的根源)
HTTP是无状态协议,为了识别用户,服务器在用户登录后返回一个Set-Cookie响应头,浏览器将Cookie保存下来。此后,浏览器在向该域名发起任何请求时(无论是点击链接、加载图片、还是AJAX请求),都会自动在请求头中携带该Cookie------这就是CSRF得以成立的根本。
2. 攻击的三步链路(经典攻击流程)
假设攻击者想在受害者不知情的情况下,修改其邮箱地址(常用于账号接管)。
-
第一步(登录) :用户登录了
bank.com,浏览器得到SESSIONID=abc123的Cookie。 -
第二步(诱导) :攻击者构造一个恶意页面
evil.com,内含一个隐藏表单或图片标签:html<!-- 利用img标签自动加载,无需用户任何操作 --> <img src="https://bank.com/changeEmail?newEmail=hacker@evil.com" />或者更隐蔽的自动提交表单:
html<form action="https://bank.com/changeEmail" method="POST" id="hackForm"> <input type="hidden" name="newEmail" value="hacker@evil.com" /> </form> <script>document.getElementById('hackForm').submit();</script> -
第三步(执行) :用户访问了
evil.com,浏览器自动向bank.com/changeEmail发起请求,并自动携带 了SESSIONID=abc123的Cookie。服务器验证Cookie通过,以为是用户本人操作,执行邮箱修改。
3. 为何同源策略防不住CSRF?
同源策略(SOP)确实限制了 evil.com 的JavaScript读取 bank.com 的Cookie或返回数据,但并不限制跨域请求的发起 。evil.com 可以通过 <img>、<script>、<form> 标签或 fetch 向 bank.com 发送请求,浏览器会乖乖地附上Cookie------这就是"跨站请求"的由来。
第三部分:CSRF的三大变种(攻击手法升级)
| 类型 | 触发方式 | 隐蔽性 | 典型案例 |
|---|---|---|---|
| GET型 | 利用 <img src="恶意URL"> 或 <a href="恶意URL"> |
极高(用户甚至不需要点击,页面加载即触发) | 银行转账、修改密码等GET接口 |
| POST型 | 利用自动提交的隐藏 <form> 或 fetch |
极高(页面加载瞬间提交,用户无感知) | 修改邮箱、添加管理员等POST接口 |
| JSONP/跨域型 | 利用 script 标签加载JSONP接口 |
中(需要服务端支持JSONP回调) | 早期一些API接口的CSRF漏洞 |
第四部分:终极防御 ------ 设计哲学与实现
CSRF的防御核心思想是:在请求中引入"服务端无法自动携带"的额外凭证,打破"Cookie自动携带"的信任链。
1. CSRF Token(同步令牌)------ 最经典的防御
-
设计原理 :服务端在用户登录后,生成一个随机且不可预测的Token(如UUID),存储在服务端Session或用户浏览器Cookie中,并将Token嵌入到每个表单或请求的隐藏字段或请求头中。
-
校验逻辑 :服务端处理请求时,同时校验Cookie中的SessionID和请求体/头中的Token 。由于Token是通过服务端生成的隐藏字段或请求头传递的,恶意网站
evil.com无法获取这个Token(同源策略阻止其读取),因此无法伪造有效请求。 -
关键细节 :Token绝不能只存在Cookie中(因为Cookie会被自动携带),必须放在请求体中或自定义请求头(如
X-CSRF-Token)中。
2. SameSite Cookie属性(现代浏览器的杀手锏)
这是最优雅的防御方案,不需要后端任何代码改动。
-
设计原理 :服务端设置Cookie时,增加
SameSite属性。-
SameSite=Strict:所有跨站请求(包括点击链接跳转)都不携带该Cookie。 -
SameSite=Lax(Chrome默认):部分安全跨站请求 (如<a href>跳转)携带,但POST表单、AJAX请求不携带。
-
-
效果 :当用户从
evil.com向bank.com发起请求时,如果Cookie设置了SameSite,浏览器不会携带该Cookie,攻击自然失效。
3. 验证Referer/Origin头(辅助防御)
-
原理 :检查请求头中的
Referer或Origin,确认请求来源是否为本站域。 -
缺陷 :部分浏览器或网络环境可能不发送Referer头(如HTTPS->HTTP降级),且Referer可被篡改,因此不能作为唯一防御手段。
第五部分:CSRF vs XSS ------ 攻防视角的终极对比
这是面试和日常开发中最容易混淆的两个概念,我用一张表彻底分清:
| 维度 | CSRF(跨站请求伪造) | XSS(跨站脚本攻击) |
|---|---|---|
| 攻击目标 | 用户身份(借用用户的权限执行操作) | 用户浏览器(在用户页面执行恶意脚本) |
| 攻击前提 | 用户已登录目标网站(存在有效Cookie) | 目标网站存在注入点(代码未过滤) |
| 是否需要获取Cookie | 不需要(仅利用浏览器自动携带Cookie的机制) | 需要 (脚本运行时,通过 document.cookie 读取) |
| 防御核心 | 引入不可自动携带的凭证(Token/SameSite) | 输出编码 + CSP + HttpOnly |
| 攻击后果 | 以用户身份执行操作(转账、改密、发帖) | 窃取数据 + 劫持会话 + 篡改页面 |
第六部分:给开发者的"实战避坑指南"
-
所有状态变更请求(POST/PUT/DELETE) :必须启用CSRF防御,GET请求不应产生任何副作用(RESTful规范)。
-
前后端分离项目(SPA) :Token放在请求头(如
X-CSRF-Token)中传递,避免放在Cookie中。 -
老系统改造 :如果无法快速实现Token机制,优先设置
SameSite=Lax(兼容性极好,Chrome 80+、Firefox 79+、Safari 13+均支持)。 -
双重Cookie验证(Double Submit Cookie) :如果服务端无法存储Token,可以生成随机Token同时放入Cookie和请求头中,服务端校验两者是否一致(但需注意Cookie的
SameSite保护)。