CSRF漏洞深度解析:从原理到实战的攻防指南
1. 项目概述:为什么CSRF是逻辑漏洞的“隐形杀手”?
做安全测试或者漏洞挖掘的朋友,对CSRF(Cross-Site Request Forgery,跨站请求伪造)这个名字肯定不陌生。但很多人,尤其是刚入门的朋友,常常把它和XSS(跨站脚本攻击)搞混,或者觉得它“危害不大”、“利用条件苛刻”。我刚开始接触安全时也这么想,直到在一次真实的企业渗透测试中,我们利用一个简单的CSRF漏洞,在管理员毫无察觉的情况下,批量删除了后台的所有用户数据,才真正体会到它的威力——它不直接偷你的“钥匙”(Session),而是骗你用你自己的“钥匙”去开一扇危险的门。这是一种典型的逻辑漏洞,攻击者利用的是应用在业务逻辑设计上的缺陷,而非系统底层的代码执行或内存溢出问题。
简单来说,CSRF攻击的核心在于“冒名顶替”。想象一下这个场景:你登录了网上银行(此时浏览器保存了你的登录凭证Cookie),然后你不小心点开了一个恶意网页。这个网页里藏着一个自动提交的表单,目标正是网上银行的转账接口。由于浏览器会自动带上你登录银行的Cookie,银行服务器看到这个带有合法Cookie的请求,就会认为是你本人操作的,从而成功执行转账。整个过程,攻击者完全不知道你的密码,也不需要窃取你的Cookie,他只是在“借用”你的身份和权限。这就是逻辑漏洞的可怕之处:系统每一步的验证从技术上看都没错(用户已登录,Session有效),但整个请求的发起逻辑是错的(并非用户本意)。
所以,把CSRF单纯看作一个技术点来学习是远远不够的。它更像是一个切入点,让我们去审视一个Web应用在“状态管理”和“请求授权”逻辑上是否足够健壮。从零基础到精通CSRF,不仅仅是学会几个Payload,更是建立起一套发现和防御逻辑漏洞的思维模式。这篇文章,我会结合我这些年踩过的坑和实战经验,从原理、场景、手工与工具测试方法、高级利用技巧到防御方案,带你彻底吃透CSRF。无论你是想入门安全测试的学生,还是希望提升自家产品安全性的开发者,收藏这一篇,足够你搭建起关于CSRF的完整知识体系。
2. CSRF漏洞核心原理深度拆解
要理解CSRF,我们必须深入到HTTP协议和浏览器同源策略的细节中去。很多人对原理一知半解,导致在漏洞挖掘和防御时总是抓不住重点。
2.1 攻击发生的三要素:缺一不可
一个成功的CSRF攻击,必须同时满足以下三个条件,这就像一把锁的三道机关,全部打开才能生效:
- 关键操作存在语义化动作:目标应用存在一个可以通过HTTP请求(通常是GET或POST)触发的“状态改变”操作。比如修改密码、转账、发表评论、删除项目、添加管理员等。仅仅是查询数据的操作(如查看个人资料)通常不构成严重威胁。
- 请求可预测且无不可伪造令牌:攻击者能够完全预测出触发该操作所需请求的所有参数。更重要的是,应用在处理这个请求时,没有使用随机的、一次性的、与当前用户会话绑定的令牌(即CSRF Token)进行验证。请求的鉴权完全依赖于浏览器自动携带的凭证,如Session Cookie、Basic Auth头等。
- 受害者已认证并保持会话:受害者用户必须已经登录了目标网站,并且浏览器中仍然保存着有效的会话凭证(如Cookie未过期)。同时,受害者需要访问攻击者构造的恶意页面。
这三个条件中,第二个条件是防御的关键,也是我们漏洞挖掘时重点寻找的突破点。如果应用为每个敏感表单都生成了一个唯一的、随机的CSRF Token,并验证该Token的有效性,那么即使攻击者能预测其他所有参数,也无法伪造出合法的请求。
2.2 浏览器“自动提交”机制详解
这是CSRF攻击得以实现的技术基础。浏览器的同源策略(Same-Origin Policy)限制了不同源站点之间的脚本互访,但它并没有禁止从一个源向另一个源“发送请求”。关键在于,浏览器在发送跨域请求时,会根据请求类型和内容,决定是否自动携带目标站点的凭证(如Cookie)。
- 简单请求与非简单请求:对于GET请求、以及Content-Type为
application/x-www-form-urlencoded、multipart/form-data或text/plain的POST请求,浏览器会直接发出,并自动携带Cookie。这类请求极易被CSRF利用,例如通过<img src=“https://bank.com/transfer?to=attacker&amount=1000”>标签发起的GET请求,或者通过自动提交的HTML表单发起的POST请求。 - 预检请求(Preflight):对于非简单请求(如Content-Type为
application/json或带有自定义头的请求),浏览器会先发送一个OPTIONS方法的预检请求,服务器响应允许后,才会发送真正的请求。这里有一个常见的误区:很多人认为使用AJAX发送JSON请求就能天然防御CSRF。实际上,如果服务器没有正确配置CORS(跨域资源共享)策略,在特定条件下(例如某些浏览器的旧版本或配置不当),攻击依然可能发生。更稳妥的做法是,无论请求类型如何,都对改变状态的接口实施CSRF防护。
理解这一点,就能明白为什么仅仅把GET请求改成POST请求并不能防御CSRF。攻击者完全可以构造一个隐藏的<form>表单,用JavaScript自动提交,同样可以发起POST请求。
2.3 与XSS的本质区别:权限的“窃取”与“冒用”
这是新手最容易混淆的地方。我画个简单的对比表:
| 特性 | CSRF (跨站请求伪造) | XSS (跨站脚本攻击) |
|---|---|---|
| 攻击目标 | 利用用户的登录状态,以用户身份执行非授权操作。 | 在用户浏览器中执行恶意脚本,窃取信息或进行其他攻击。 |
| 所需条件 | 用户已登录目标站点;目标站点存在逻辑漏洞(无Token验证)。 | 目标站点存在输入输出过滤不严的漏洞,允许注入脚本。 |
| 攻击者视角 | 不需要获取用户的Cookie或Session。 | 通常需要窃取Cookie、Session或其他敏感信息。 |
| 核心利用 | 伪造HTTP请求。 | 注入并执行JavaScript代码。 |
| 关系 | XSS漏洞可能导致更严重的CSRF攻击(例如,通过XSS窃取CSRF Token)。 | CSRF通常不依赖XSS,但两者结合威力巨大。 |
简单记:XSS是“在你的地盘(浏览器)里搞破坏”,而CSRF是“冒充你去别的地方(其他网站)干坏事”。一个关注“执行环境”,一个关注“请求身份”。
3. 手工挖掘CSRF漏洞的实战流程
知道了原理,我们就要上手找。手工挖掘CSRF漏洞,是一个需要耐心和细致观察的过程。下面是我常用的一套流程,适用于黑盒测试场景。
3.1 信息收集与功能点梳理
在开始测试前,不要急着上工具。先像普通用户一样,把目标网站(Web Application)或者系统(如ThinkPHP开发的后台)完整地浏览和使用一遍。这一步的目标是绘制一张“攻击面地图”。
- 枚举所有功能点:注册、登录、修改个人资料(邮箱、密码、手机号)、发表内容(文章、评论)、管理操作(删除用户、审核内容、调整权限)、支付、地址管理、消息设置等。用思维导图工具记录下来。
- 重点关注状态改变操作:在所有功能点中,标记出所有会导致服务器数据发生“增删改”的操作。这些是CSRF的潜在高危点。例如,“修改邮箱”比“查看邮箱”危险得多。
- 记录请求特征:使用浏览器开发者工具(F12),切换到Network(网络)面板,并勾选“Preserve log”(保留日志)。然后,手动触发每一个你标记出的高危操作。
- 观察请求方法:是GET还是POST?PUT、DELETE等方法是否被使用?
- 观察请求参数:除了业务参数(如new_email, old_password),有没有一个看起来是随机字符串的参数?它的参数名通常是
csrf_token,authenticity_token,_token等。这是防御CSRF的关键信号。 - 观察认证方式:请求头(Headers)里,认证是依靠Cookie中的Session ID,还是Authorization头?Cookies是否设置了
HttpOnly和Secure属性?(这虽然不能防CSRF,但能增加攻击难度)。
实操心得:很多现代框架(如Laravel, Django, Spring Security)默认会为表单注入CSRF Token。但开发者可能会因为“麻烦”或“觉得没必要”而在某些API接口上手动关闭它。所以,不要看到登录表单有Token就以为万事大吉,要测试每一个敏感端点。
3.2 漏洞验证:构造与触发PoC
当你发现一个敏感操作(比如修改密码)的请求中,没有CSRF Token之类的不可预测参数,并且鉴权仅靠Cookie时,就可以开始验证了。
1. 验证GET型CSRF:这是最简单的一种。如果修改密码的接口是GET /change_password?new_password=123456。
- 构造PoC:直接创建一个HTML文件,内容如下:
<img src="http://target.com/change_password?new_password=hacked123" />- 触发:让已登录目标网站的用户访问这个HTML页面。如果他的密码被修改,漏洞存在。
- 风险:由于GET请求可能被浏览器预加载、被网络设备日志记录,更容易意外触发。
2. 验证POST型CSRF:更常见的情况。假设修改邮箱的接口是POST /update_email,参数为email=new@attacker.com。
- 构造PoC:创建一个自动提交表单的HTML文件。
<!DOCTYPE html> <html> <body> <form id="csrfForm" action="http://target.com/update_email" method="POST"> <input type="hidden" name="email" value="attacker@evil.com" /> <!-- 如果有其他必填参数,也需要一并隐藏填入 --> <input type="hidden" name="user_id" value="123" /> </form> <script> // 页面加载后自动提交表单 document.getElementById('csrfForm').submit(); </script> </body> </html>- 触发:用户访问此页面,表单会自动提交,完成邮箱修改。
3. 验证JSON型或复杂POST请求:有些应用使用AJAX发送JSON数据。此时需要利用<form>的enctype="text/plain"或通过构造Flash攻击(已逐渐淘汰)等方式,但更通用的方法是,如果该应用同时支持application/x-www-form-urlencoded格式,则优先测试该格式。如果只支持JSON,则需要检查CORS策略和是否有其他验证缺陷。
避坑指南:在本地验证时,务必确保测试环境与目标网站域名不同(例如,用本地file协议或自己搭建的HTTP服务器),以模拟真实的跨站场景。同时,浏览器的第三方Cookie策略可能会影响测试结果,需要根据实际情况调整浏览器设置。
3.3 绕过常见防御机制的技巧
现在的Web应用多少都有些防护,直接裸奔的接口变少了。这时候就需要一些绕过技巧。
1. Token在哪?—— 寻找Token的生成与验证逻辑
- Token在表单中:这是最常见的方式。检查HTML源码,寻找隐藏的input字段。如果Token存在,但未与用户会话绑定(例如,所有用户的Token都一样),或者验证后未销毁(可重复使用),则依然存在漏洞。
- Token在Cookie中:有些框架(如Django早期版本)会将Token放在Cookie里,提交时再从Cookie中读取并验证。这需要检查服务器是否严格对比了Cookie中的Token和请求体(或参数)中的Token。如果服务器只检查了Cookie里的Token,而请求中可以不提供,或者对比逻辑有误,则存在漏洞。
- Token在请求头中:通过自定义Header(如
X-CSRF-Token)传递。这通常需要配合JavaScript(AJAX)使用。在纯HTML表单提交的场景下,攻击者无法为跨域请求添加自定义头,因此这种防护是有效的。但是,如果网站同时存在任何XSS漏洞,攻击者就可以利用XSS读取Token并构造请求,实现“组合拳”攻击。
2. 检查Referer/Origin头?—— 验证逻辑可被绕过很多应用会检查HTTP请求头中的Referer或Origin字段,判断请求来源是否合法。
- 空Referer:如果服务器在
Referer为空时通过了检查(例如使用if not referer or referer.startswith(‘https://target.com’)这种错误逻辑),攻击者可以通过在HTTPS页面中发起HTTP请求,或者使用<meta name=“referrer” content=“no-referrer”>标签,使浏览器发送空Referer,从而绕过检查。 - Origin头:
Origin头通常用于CORS场景,且不能被meta标签控制。但如果服务器验证逻辑不严谨(如仅检查域名部分,不检查协议和端口),或者存在域名解析、重定向方面的漏洞,也可能被绕过。 - 实战技巧:在测试时,用Burp Suite等工具抓包,手动删除或修改
Referer和Origin头,观察服务器响应。如果删除后请求依然成功,说明防护无效。
3. 双重Cookie验证?—— 警惕逻辑缺陷这是一种较弱的防御方式:在请求参数或Body中也需要携带Cookie中的某个值(如session_id)。由于浏览器的同源策略,攻击者无法通过CSRF页面读取目标站的Cookie,因此他无法知道这个值。这看起来是安全的。但是,如果网站存在任何子域可控、或者Cookie设置过于宽松(如Set-Cookie: session_id=xxx; Domain=.example.com),攻击者可能在其控制的子域上设置Cookie,从而污染父域的Cookie,实现攻击。
4. 利用工具高效挖掘与利用CSRF
手工测试虽然透彻,但效率较低。在实际的漏洞挖掘(尤其是SRC、EDUSRC项目)或渗透测试中,我们通常需要借助工具进行辅助和加速。
4.1 被动扫描与主动探测(Burp Suite, OWASP ZAP)
专业的安全测试人员离不开Burp Suite。它的“Scanner”和“CSRF PoC Generator”功能是挖掘CSRF的利器。
- 配置代理与爬虫:将浏览器代理设置为Burp,开启代理拦截(Proxy -> Intercept is on)。然后使用浏览器正常浏览目标网站的所有功能。Burp的“Target” -> “Site map”会自动记录下所有的请求。
- 启动被动扫描:在Site map中,右键点击你的目标主机或目录,选择 “Actively scan this branch”。Burp会自动对已发现的请求进行漏洞扫描,其中就包括CSRF检测。它主要通过分析请求参数,判断是否存在明显的Token缺失。
- 主动生成PoC:对于任何一个你怀疑的请求(比如修改密码的POST请求),在Proxy历史记录或Repeater模块中右键点击该请求,选择“Engagement tools” -> “Generate CSRF PoC”。
- Burp会自动生成一个包含所有参数的HTML表单。
- 你可以在这个基础上进行修改,比如删除不必要的参数,调整提交方式等。
- 生成的PoC可以直接在浏览器中打开进行测试,极大地提高了验证效率。
注意事项:自动化工具的扫描结果会有误报和漏报。它可能误将一些有Token但Token可预测或可重放的请求报为安全,也可能漏掉一些需要特定上下文(如多步操作)的复杂CSRF。因此,工具报告需要结合人工审计来确认。
4.2 半自动化测试脚本编写
对于需要批量测试大量接口,或者测试逻辑复杂的多步CSRF(例如,先请求A页面获取Token,再用这个Token去请求B接口),编写简单的Python脚本会更高效。
下面是一个使用requests库和BeautifulSoup库测试“无Token表单”的简化示例脚本思路:
import requests from bs4 import BeautifulSoup session = requests.Session() # 1. 首先登录,获取有效的会话Cookie login_data = {'username': 'test_user', 'password': 'test_pass'} login_resp = session.post('http://target.com/login', data=login_data) # 2. 访问一个敏感功能页面,如修改邮箱页 profile_page = session.get('http://target.com/profile/edit') # 3. 解析页面,查找表单 soup = BeautifulSoup(profile_page.text, 'html.parser') forms = soup.find_all('form') for form in forms: action = form.get('action') method = form.get('method', 'get').lower() inputs = form.find_all('input') # 4. 检查表单中是否有名为 csrf_token, _token 等的隐藏字段 has_csrf_token = False for inp in inputs: if inp.get('type') == 'hidden' and 'token' in inp.get('name', '').lower(): has_csrf_token = True break # 5. 如果没有找到CSRF Token,则记录下这个表单的详细信息,供后续手动验证 if not has_csrf_token and action: print(f"[!] 潜在CSRF漏洞表单: {method.upper()} {action}") print(f" 表单参数: {[inp.get('name') for inp in inputs if inp.get('name')]}")这个脚本可以帮助你快速筛选出未受保护的表单,然后你再针对这些表单进行深入的手工PoC构造和验证。
4.3 针对特定框架的测试思路
如热搜词中提到的ThinkPHP、IIS等,针对特定框架或中间件,CSRF的挖掘可能有其特殊性。
- ThinkPHP:老版本的ThinkPHP(如3.x)内置的CSRF防护需要开发者手动开启。如果开发者在配置文件中未开启
‘csrf_protection’ => true,或者在写表单时未使用{:token()}函数生成令牌,那么整个应用可能处于无防护状态。测试时,可以查看表单页面源码,搜索token或__hash__等关键字。 - IIS/.NET:ASP.NET WebForms 使用
ViewState作为状态管理机制,其中包含一个ViewStateUserKey可用于防御CSRF,但并非默认启用。ASP.NET MVC 则提供了AntiForgeryToken机制。测试时,检查表单中是否存在__RequestVerificationToken这个隐藏字段。 - Spring Security:默认情况下,Spring Security为启用CSRF防护。但它可能为某些路径(如登录接口、静态资源)禁用防护。检查Spring Security的配置,看是否存在
csrf().disable()或对特定路径的permitAll()配置。
了解目标的技术栈,能让你更有针对性地进行测试,事半功倍。
5. 从原理到实战:高级CSRF攻击场景剖析
掌握了基础验证方法,我们来看几个更复杂、更贴近真实网络环境的攻击场景。这些场景往往能绕过一些简单的防护,危害也更大。
5.1 基于JSON的CSRF攻击
现代前端应用大量使用AJAX发送JSON数据。如果服务器端没有正确验证Content-Type或Origin,攻击依然可能发生。
攻击场景:一个修改用户昵称的API,只接受application/json格式。
POST /api/user/update_nickname HTTP/1.1 Host: target.com Content-Type: application/json Cookie: sessionid=abc123 {"nickname": "NewNick"}绕过尝试:攻击者构造一个带有enctype="text/plain"的表单。当表单以此编码提交时,浏览器会将数据以纯文本形式发送,且Content-Type为text/plain。如果服务器端解析JSON的库不够健壮(例如,先尝试解析JSON,失败后再按其他方式解析),可能会意外地处理这个请求。
<form action="http://target.com/api/user/update_nickname" method="POST" enctype="text/plain"> <input name='{"nickname":"Hacked","ignore":"' value='test"}' type='hidden'> </form> <script>document.forms[0].submit();</script>提交后,请求体可能是{"nickname":"Hacked","ignore":"=test"}。某些JSON解析器可能会忽略末尾的=test”},从而成功解析出nickname: Hacked。
核心要点:防御此类攻击,服务器端必须严格校验
Content-Type头为application/json,并且使用健壮的JSON解析器,在解析失败时直接拒绝请求。
5.2 组合漏洞攻击:XSS + CSRF
这是威力巨大的组合拳。一个反射型XSS漏洞可能只能弹个窗,但如果利用它来窃取CSRF Token,就能发起高权限的CSRF攻击。
攻击链:
- 攻击者发现目标站
profile.php页面存在反射型XSS,参数name未过滤直接输出。 - 该站点的“修改密码”功能有CSRF Token防护。
- 攻击者构造一个恶意链接:
http://target.com/profile.php?name=<script>fetch(‘/change_password_page’).then(r=>r.text()).then(html=>{var token=html.match(/csrf_token” value=”(.*?)”/)[1]; fetch(‘/change_password’, {method:‘POST’, body:‘new_pass=hacked&csrf_token=’+token}); })</script> - 管理员点击此链接后,脚本会在管理员浏览器中执行:先访问修改密码页面,用正则表达式提取出页面中的CSRF Token,然后用这个Token发起一个修改密码的合法请求。由于请求发自管理员浏览器,且带有正确的Token,攻击成功。
这种攻击完全绕过了独立的CSRF防护,将危害提升到了新的等级。它告诉我们,安全是一个整体,一处短板可能导致全线崩溃。
5.3 针对内部网络或本地应用的CSRF
CSRF不仅可以攻击公网应用,在内部网络或浏览器访问的本地服务(如路由器管理界面192.168.1.1,或本地开发服务器localhost:3000)上同样有效。
场景:公司内网有一个简陋的设备管理系统,部署在http://10.0.0.5,用于重启服务器。该页面无CSRF防护。攻击:一个内部员工在办公电脑上登录了这个系统。他偶然点开了另一个同事分享的“搞笑图片”页面(该页面托管在内网另一台服务器上)。这个页面里隐藏了一个<img src=“http://10.0.0.5/reboot?server=db01”>标签。于是,数据库服务器被意外重启。
这种攻击在内部安全测试中尤其需要关注,因为内网应用的安全意识往往更薄弱。
6. 企业级防御方案设计与落地
作为防御方,我们该如何系统地构建CSRF防护体系?这里提供一套从架构到代码的落地方案。
6.1 同步令牌模式:最可靠的方案
这是目前最主流、最有效的防御方案,被各大Web框架广泛采用。
核心流程:
- 生成令牌:当用户会话建立时,服务器生成一个高强度随机数(Token),将其存储在服务器端(如Session中),同时发送给客户端。
- 客户端携带:对于需要防护的请求(通常是所有非幂等的POST/PUT/DELETE请求),客户端必须将这个Token作为参数(表单隐藏域)或请求头(如
X-CSRF-Token)一并提交。对于传统表单,Token放在隐藏域;对于AJAX请求,可以从Cookie或Meta标签读取,然后放入请求头。 - 服务器验证:服务器收到请求后,比较客户端提交的Token和服务器Session中存储的Token是否一致且未过期。一致则通过,否则拒绝。
关键实现细节与避坑点:
- Token的绑定:Token必须与当前用户会话绑定。全局统一的Token是无效的。
- Token的随机性与强度:使用安全的随机数生成器(如操作系统的CSPRNG),确保Token不可预测。
- 每会话或每表单:可以采用“每会话一个Token”(简单,但有一定重放风险)或“每个表单一个Token”(更安全,但实现复杂)。对于安全性要求极高的操作(如转账),建议使用一次性的Token。
- AJAX请求的处理:对于单页面应用(SPA),可以将Token放在页面的
<meta>标签中,或由登录接口返回,然后由JavaScript在每次请求时将其添加到请求头(如X-CSRF-Token)。切记,不能依赖Cookie自动携带,因为攻击者无法自定义请求头。 - 敏感Cookie属性:为Session Cookie设置
Secure(仅HTTPS传输)和HttpOnly(禁止JavaScript读取)属性。这虽不能防CSRF,但能防止Token通过XSS被窃取,是重要的辅助防御措施。
6.2 双重Cookie验证:一种补充思路
如前所述,将Token放在Cookie中,请求时再从Cookie中读取并验证。这种方案在正确实现下是有效的,但需要注意:
- Cookie作用域:必须确保CSRF Token的Cookie作用域严格,避免被子域污染。
- Token存储:Token仍需在服务器端有对应记录,用于验证,不能仅做字符串比对。
- 适用场景:更适合API接口,方便前端JavaScript统一从Cookie读取并设置到请求头中。
6.3 同源检测与自定义头:简单有效
检查Origin/Referer头:这是一个低成本且有效的辅助手段。服务器可以检查请求头中的Origin(优先)或Referer字段,判断请求是否来源于可信的域名。
- 优点:实现简单,对用户透明。
- 缺点:存在被绕过的风险(如前文所述),且在某些合法场景下(如从HTTPS跳转到HTTP,或用户隐私设置)这些头可能为空或被移除,需要做好兼容处理(例如,对于空Referer的请求,可以要求额外的验证)。
- 建议:不要将其作为唯一的防御手段,而是作为同步令牌模式的一道额外防线。
使用自定义请求头:让前端在所有“状态改变”请求的Header中添加一个自定义字段,如X-Requested-With: XMLHttpRequest。由于浏览器同源策略限制,攻击者无法通过CSRF跨域添加自定义头。
- 优点:对于纯AJAX应用非常有效且简单。
- 缺点:对于传统表单提交无效;如果网站支持CORS并配置了允许自定义头,则此方法失效。
6.4 框架内置防护的最佳实践
绝大多数现代Web开发框架都内置了CSRF防护模块。最佳实践就是:除非有极其特殊的理由,否则不要关闭它。
- Django:使用
{% csrf_token %}模板标签。确保中间件django.middleware.csrf.CsrfViewMiddleware已启用。对于AJAX请求,需要从Cookie获取Token并设置X-CSRFTOKEN头。 - Spring Security (Java):默认启用。确保配置中不要随意调用
csrf().disable()。前端提交表单时,Thymeleaf等模板引擎会自动添加_csrf令牌。 - Laravel (PHP):为每个活跃用户会话生成CSRF令牌,并通过
@csrfBlade指令注入表单。验证由VerifyCsrfToken中间件自动完成。 - Express.js + csurf middleware:使用
csurf中间件。注意在前后端分离项目中,需要正确配置Token的获取和提交方式。
开发中的常见错误:
- 为API接口全局禁用CSRF:认为API只用Token认证(如JWT)就安全了。但若JWT Token存储在
localStorage中,仍需防范通过XSS发起的CSRF式攻击(虽然不叫传统CSRF,但逻辑类似)。对于API,建议使用同步令牌或严格校验Origin头。 - Token泄露:通过XSS漏洞、不安全的日志、错误的API响应体泄露了CSRF Token。
- 验证逻辑错误:例如,对比Token时未考虑大小写或空格,导致验证绕过。
7. 漏洞挖掘实战中的疑难问题排查
在实际测试中,你可能会遇到一些“奇怪”的情况,让你不确定漏洞是否存在。这里记录几个常见的疑难场景和排查思路。
7.1 状态码成功但业务未生效
你用PoC发起了请求,服务器返回了200 OK,甚至返回了成功的JSON消息{“code”: 200, “msg”: “success”},但回到网站查看,发现数据并没有被修改。
- 可能原因1:二次确认:很多关键操作(如删除、支付)有前端二次确认弹窗。你的PoC绕过了前端JavaScript,直接请求了后端接口,但后端接口可能设计为只接收来自前端特定流程的请求(例如,需要先请求一个
get_confirm_token的接口)。你需要分析完整的前端交互流程。 - 可能原因2:操作日志与审计:后端确实执行了操作,但触发了风控或审计规则,操作被自动回滚或标记为待审核。检查是否有相关的通知或日志功能。
- 排查方法:使用Burp的Repeater模块,完全模拟浏览器的正常操作流程,从第一步开始,按顺序发送请求,观察每个请求的响应和后续请求的参数变化。
7.2 存在Token但似乎可重用
你发现请求中有Token,但测试发现,同一个Token似乎可以多次使用。
- 测试方法:用同一个Token,更换关键参数(如新的邮箱地址),重复发送请求。如果两次都成功,说明Token未与操作内容绑定,存在重放攻击风险。
- 深入测试:尝试将用户A的Token用于用户B的会话(需要先窃取Token)。如果也成功,说明Token未与用户会话严格绑定,这是严重漏洞。
- 结论:一个健壮的Token应该是一次性的(使用后即失效)或与请求参数进行哈希绑定的(例如,Token = HMAC(secret_key, session_id + action_param)),确保Token只能用于特定的操作。
7.3 依赖Referer验证的边界情况
目标站点检查Referer头,你的空Referer攻击失败了。但你不甘心,觉得可能有其他绕过方式。
- 测试子域与协议:如果目标站点是
https://app.target.com,尝试构造来源为http://app.target.com(协议不同)、https://dev.target.com(子域不同)、https://target.com(根域)的请求,看服务器是否放行。有些正则匹配写得不严谨,可能只匹配了target.com。 - 利用URL解析差异:尝试在Referer中注入路径、参数或片段标识符,如
https://target.com.evil.com/或https://target.com@evil.com/。古老的浏览器或服务器解析库可能对此解析有误。 - 终极建议:即使找到了绕过方法,在漏洞报告中也应明确指出“依赖Referer验证是不安全的”,并推荐采用同步令牌方案。
挖掘CSRF漏洞,尤其是逻辑复杂的漏洞,需要像侦探一样思考,耐心地追踪每一个请求和响应,理解应用背后的完整状态流转。这个过程本身,就是对Web应用安全逻辑的深刻训练。
