Skip to content
On this page

CSRF 防护

跨站请求伪造(Cross-Site Request Forgery,CSRF)是一种攻击方式,攻击者诱导用户在已认证的 Web 应用程序上执行非预期的操作。

CSRF 攻击原理

CSRF 攻击利用了浏览器的自动发送 Cookie 和认证凭据的特性。当用户登录了某个网站后,浏览器会自动在后续请求中包含该网站的认证信息,攻击者利用这一点来伪造用户请求。

攻击示例:

假设用户已登录银行网站 bank.example.com,并且有一个转账功能:

html
<!-- 银行转账页面 -->
<form action="https://bank.example.com/transfer" method="POST">
  <input type="hidden" name="toAccount" value="attacker_account" />
  <input type="hidden" name="amount" value="10000" />
  <input type="submit" value="Transfer Money" />
</form>

攻击者可能会创建一个恶意页面:

html
<!-- 恶意网站上的隐藏表单 -->
<html>
<head>
  <title>Free Gift!</title>
</head>
<body>
  <img src="https://bank.example.com/transfer?toAccount=attacker&amount=10000" width="0" height="0" />
  <!-- 或者使用表单自动提交 -->
  <form id="csrfForm" action="https://bank.example.com/transfer" method="POST">
    <input type="hidden" name="toAccount" value="attacker_account" />
    <input type="hidden" name="amount" value="10000" />
  </form>
  <script>document.getElementById('csrfForm').submit();</script>
</body>
</html>

当用户访问这个恶意页面时,如果他们仍处于银行网站的登录状态,转账操作就会自动执行。

CSRF 攻击类型

GET 请求攻击

通过图像标签、iframe 等发起 GET 请求:

html
<img src="https://bank.example.com/transfer?toAccount=attacker&amount=10000" />

POST 请求攻击

通过自动提交的表单发起 POST 请求:

html
<form action="https://bank.example.com/transfer" method="POST">
  <input type="hidden" name="toAccount" value="attacker" />
  <input type="hidden" name="amount" value="10000" />
</form>
<script>document.forms[0].submit();</script>

CSRF 防护措施

1. CSRF Token

在表单中添加随机生成的令牌:

html
<form action="/transfer" method="POST">
  <input type="hidden" name="csrf_token" value="random_generated_token" />
  <input type="text" name="toAccount" />
  <input type="text" name="amount" />
  <button type="submit">Transfer</button>
</form>

服务器端验证令牌:

javascript
// Express.js 示例
app.post('/transfer', (req, res) => {
  const { csrf_token, toAccount, amount } = req.body;
  
  // 验证 CSRF token
  if (!validateCsrfToken(csrf_token, req.session)) {
    return res.status(403).send('Invalid CSRF token');
  }
  
  // 执行转账操作
  transferMoney(toAccount, amount);
});

设置 Cookie 的 SameSite 属性:

Set-Cookie: sessionId=abc123; SameSite=Strict

SameSite 属性的值:

  • Strict:完全禁止跨站请求携带 Cookie
  • Lax:允许部分跨站请求(如链接跳转)
  • None:允许所有跨站请求(需要 Secure 属性)

将 CSRF token 同时存储在 Cookie 和表单中,服务器验证两者是否匹配:

javascript
// 设置 CSRF token 到 Cookie
res.cookie('csrf_token', generateCsrfToken(), { httpOnly: false });

// 在表单中也需要包含同样的 token
const csrfToken = req.cookies.csrf_token;
if (csrfToken !== req.body.csrf_token) {
  return res.status(403).send('CSRF token mismatch');
}

4. 验证 Referer 头部

检查请求的来源:

javascript
app.use((req, res, next) => {
  const allowedReferers = ['https://yourdomain.com'];
  const referer = req.get('Referer');
  
  if (referer && !allowedReferers.some(allowed => referer.startsWith(allowed))) {
    return res.status(403).send('Invalid referer');
  }
  
  next();
});

5. 自定义头部验证

要求请求包含自定义头部:

javascript
// 前端 JavaScript
fetch('/api/transfer', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Requested-With': 'XMLHttpRequest',
    'X-CSRF-Token': getCsrfToken()
  },
  body: JSON.stringify({ toAccount, amount })
});

框架中的 CSRF 防护

Express.js with csurf

javascript
const csrf = require('csurf');

const csrfProtection = csrf({ cookie: true });

app.use(csrfProtection);

app.get('/form', (req, res) => {
  res.render('send-money-form', { csrfToken: req.csrfToken() });
});

app.post('/transfer', csrfProtection, (req, res) => {
  // 处理转账请求
});

Django

Django 自带 CSRF 中间件:

python
from django.views.decorators.csrf import csrf_protect
from django.middleware.csrf import csrf_exempt

@csrf_protect
def transfer_view(request):
    if request.method == 'POST':
        # 处理转账请求
        pass

在模板中:

html
{% csrf_token %}
<form method="post">
  {% csrf_token %}
  <!-- 表单内容 -->
</form>

ASP.NET Core

csharp
public class TransferController : Controller
{
    [HttpPost]
    [ValidateAntiForgeryToken]
    public IActionResult Transfer(TransferModel model)
    {
        // 处理转账请求
        return View();
    }
}

在视图中:

html
<form method="post" asp-action="Transfer">
  @Html.AntiForgeryToken()
  <!-- 表单内容 -->
</form>

检测 CSRF 漏洞

手动测试

  1. 登录目标网站
  2. 打开另一个浏览器窗口(无登录状态)
  3. 尝试直接访问需要认证的操作
  4. 检查是否可以绕过身份验证

自动化工具

  • OWASP ZAP
  • Burp Suite
  • CSRFTester

最佳实践

  1. 使用 CSRF Token - 对所有修改状态的请求使用 CSRF 令牌
  2. 设置 SameSite Cookie - 为认证 Cookie 设置适当的 SameSite 属性
  3. 验证请求来源 - 检查 Origin 和 Referer 头部
  4. 区分敏感操作 - 对敏感操作实施额外的安全检查
  5. 定期评估 - 定期审查和测试 CSRF 防护措施
  6. 安全培训 - 确保开发团队了解 CSRF 风险和防护措施

总结

CSRF 是一种常见的 Web 安全威胁,但可以通过多种技术手段有效防护。最佳做法是结合使用多种防护措施,创建多层安全防护体系。