跨站请求伪造(Cross-Site Request Forgery,简称CSRF)是一种利用Web应用对用户身份信任的漏洞攻击方式。攻击者通过伪造合法请求,让已登录用户在不知情的情况下触发非预期的操作。例如用户登录了邮箱,攻击者在其访问的恶意页面中嵌入一个自动提交的表单,指向邮箱的修改密码接口。由于浏览器会自动携带该域名下的Cookie,服务器会认为这是一次来自用户本人的合法请求,从而完成密码修改。与XSS不同,CSRF并不需要窃取Cookie,而是直接借用浏览器的行为,因此防御思路也截然不同。

一、CSRF攻击的工作原理与典型场景
CSRF能够成立依赖于三个前提条件:用户已登录目标站点并持有有效会话;目标站点存在一个可以改变状态的请求,如表单提交、删除操作;攻击者能够构造该请求并诱导用户触发。浏览器在发送请求时会自动带上目标域的Cookie,这是Web会话管理的基本机制,也是CSRF利用的根源。攻击者通常会在第三方页面植入隐藏的HTML表单或利用img标签的src属性发送GET请求,当用户访问该页面时,请求在后台自动完成。
典型场景包括:用户在论坛点击了一个看似正常的图片链接,实际上该链接是删除自己在购物网站收货地址的接口;或者攻击者发送一封带链的邮件,用户点击后触发银行卡转账请求。由于这些请求来自用户浏览器,IP和Cookie都合法,服务器难以从表面区分是否为用户真实意图。更危险的是,如果目标接口只使用GET方法处理状态变更,攻击者甚至只需要一个img标签就能完成攻击。
来看一个简化示例。假设某网站修改邮箱的接口为POST /user/updateEmail,且仅依赖登录态Cookie进行身份验证。攻击者在自己控制的页面中放置一个隐藏表单,其action指向目标站点的 /user/updateEmail 接口,method为POST,并带有名称为email、值为攻击者邮箱的输入项,同时通过一段脚本在页面加载时自动提交该表单。用户只要在登录状态下访问该恶意页面,邮箱就会被悄然改绑。在真实攻击中,这段代码可以隐藏在图片加载、广告脚本或评论区的恶意内容里,因此不能因为请求来自用户浏览器就认为安全。
二、网站漏洞检测中如何发现CSRF风险
安全测试人员在检测CSRF漏洞时,首先要梳理所有会改变服务器状态的请求,包括修改密码、绑定手机、转账、删除数据、发布内容等。然后逐个分析这些请求是否具备有效的防CSRF机制。核心判断标准有三项:请求中是否包含随机且不可预测的Token;服务端是否验证Referer或Origin头;Cookie是否设置了SameSite属性。如果三者均缺失或形同虚设,则很可能存在CSRF风险。
实际操作中,可以使用Burp Suite等代理工具拦截关键请求,观察参数中是否有csrf_token、_token、authenticity_token等字段。如果请求参数固定不变或完全可预测,攻击者就能提前构造恶意请求。还可以尝试删除Token参数后重放请求,若服务端依然返回成功,说明Token校验未生效。对于Referer检查,可以修改Referer为攻击者域名或置空,观察响应是否正常。需要注意的是,有些低版本浏览器或隐私设置会丢弃Referer,因此仅依赖Referer校验并不可靠。
除了手工检测,自动化扫描工具如OWASP ZAP、Acunetix等也能辅助发现部分CSRF问题。但工具往往只能识别明显缺失Token的情况,对于Token生成逻辑薄弱、校验可绕过、SameSite配置错误等隐蔽问题,仍需要人工代码审计。审计时应重点关注身份认证之后的状态变更接口,尤其是那些既有GET又有POST的实现,例如某些框架允许GET请求调用同一处理函数,这会显著扩大攻击面。
一个常见检测思路:先正常登录目标站点,抓取一个状态变更请求,复制为CSRF PoC表单,放到本地HTML文件中打开。如果目标接口在没有额外校验的情况下执行了操作,就证明漏洞存在。测试时务必使用自己授权的测试账号和测试环境,避免影响真实用户数据。
三、CSRF代码加固方案与实战示例
加固CSRF漏洞的核心思路是让服务端能够区分请求是否来自用户真实操作。最有效的方案是引入CSRF Token:服务端为用户会话生成一个随机、不可预测的字符串,嵌入前端表单中的隐藏字段或自定义请求头中。当请求到达时,服务端从请求参数或头部取出Token并与会话中存储的值比对,不一致则拒绝请求。Token必须在每次会话或每次请求时更新,且长度足够,如32字节以上,使用安全随机数生成器生成。
以Java Spring Security为例,框架默认开启了CSRF防护,会在表单提交时校验_csrf参数。对于前后端分离架构,可以在登录接口返回时下发CSRF Token,前端将其存入内存或localStorage,并在后续Ajax请求中通过X-CSRF-Token头部携带。服务端过滤器校验该头部与Session中Token是否一致。对于Node.js的Express框架,可使用csurf中间件,在路由处理前进行token验证。
下面是一个使用文字描述的加固流程:第一步,用户登录成功后,服务端生成随机Token并存储到Session中。第二步,在HTML表单中增加隐藏字段,其name为_csrf,value为服务端输出的Token。第三步,前端提交表单时自动带上该字段。第四步,服务端在控制器或中间件中取出Session中的Token与请求参数中的Token进行字符串比较,若不一致则返回403状态码并终止操作。第五步,对于Ajax请求,前端从Cookie或响应头中读取Token,放入X-CSRF-Token请求头,服务端按同样逻辑校验。
值得注意的是,Token不能放在URL查询参数中,因为URL可能被浏览器历史、日志或Referer泄露。同时,Token不能简单用用户ID、时间戳或可预测的哈希值,否则攻击者可自行计算。另一层有效防护是设置Cookie的SameSite属性。SameSite=Strict表示跨站请求一律不携带Cookie,可彻底阻断CSRF;SameSite=Lax则允许顶级导航携带Cookie,但禁止第三方POST请求携带。现代浏览器普遍支持SameSite,但需注意旧版本兼容性。对于涉及资金或敏感数据的操作,还应叠加二次验证,如输入支付密码、短信验证码或重新登录,即使Token被绕过也能降低损失。
以下表格对比了不同加固措施的优缺点,供实战选择:
| 加固措施 | 防护效果 | 实现难度 | 注意事项 |
|---|---|---|---|
| CSRF Token | 高 | 中 | 需保证随机性和校验一致性 |
| SameSite Cookie | 高 | 低 | 需兼顾跨站跳转场景 |
| Referer或Origin校验 | 中 | 低 | 可能被代理或隐私设置绕过 |
| 二次验证 | 高 | 中 | 用户体验有一定影响 |
| 自定义请求头 | 高 | 中 | 仅适用于Ajax请求 |
四、常见误区与纵深防御策略
很多开发者认为使用POST代替GET就能防止CSRF,这其实是一个常见误区。攻击者完全可以在第三方页面中构造自动提交的POST表单,POST本身并不能阻止跨站请求。另一个误区是仅在前端做Token隐藏,认为用户看不到就安全。CSRF攻击发生在用户浏览器中,前端隐藏字段对攻击者毫无障碍,必须由服务端严格校验。还有些团队只对登录接口做了Token校验,却忽略资料修改、消息发送等低感知接口,这些同样可能被利用。
纵深防御要求不能依赖单一措施。实践中可以组合使用CSRF Token与SameSite属性,并在关键操作上增加二次验证。同时,定期对代码仓库进行安全审计,使用静态代码扫描工具检测所有状态变更接口是否都经过防CSRF中间件。对于第三方集成的支付、OAuth回调等接口,也要纳入防护范围,因为攻击者可能利用回调URL发起CSRF。
此外,安全团队应建立CSRF检测基线:所有改变状态的接口必须使用非GET方法;必须包含随机Token或自定义请求头校验;Cookie尽量设置SameSite等于Lax或Strict;对敏感操作强制二次认证。上线前进行渗透测试,模拟攻击者构造PoC验证修复效果。只有将安全要求落实到开发框架和代码规范中,才能从根源上减少CSRF漏洞的出现。