CSRF在服务器跨域攻击的防御原理

一、CSRF攻击原理

什么是CSRF(跨站请求伪造)?

CSRF是一种让用户在不知情的情况下,以他们的身份执行非本意操作的攻击。

攻击流程示例:

  1. 用户登录银行网站 bank.com,登录后浏览器保存了会话cookie
  2. 用户访问恶意网站 evil.com
  3. 恶意网站包含一个自动提交的表单
  4. 浏览器会自动带上bank.com的cookie,完成转账操作
<form action="https://bank.com/transfer" method="POST">
  <input type="hidden" name="amount" value="10000">
  <input type="hidden" name="to" value="hacker">
</form>
<script>document.forms[0].submit();</script>
HTML

二、CSRF令牌防御机制

核心思想:引入一个攻击者无法获取的”秘密令牌”

Flask-WTF的实现:

1. 令牌生成

# Flask-WTF内部会为每个会话生成唯一的令牌
csrf_token = generate_random_string()  # 比如:'abc123xyz'
session['csrf_token'] = csrf_token  # 存储在服务器端会话中
Python

2. 令牌嵌入表单

<!-- 在表单中嵌入令牌 -->
<form method="POST">
  <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
  <!-- csrf_token() 会从session中取出令牌 -->
  <input type="text" name="amount">
</form>
HTML

3. 令牌验证流程

# Flask-WTF验证逻辑简化版
def validate_csrf():
    # 1. 获取表单提交的令牌
    form_token = request.form.get('csrf_token')
    
    # 2. 获取服务器存储的令牌
    session_token = session.get('csrf_token')
    
    # 3. 严格比较(防时序攻击)
    if not compare_digest(form_token, session_token):
        abort(403)  # 验证失败
    
    # 4. 每次验证后可选:刷新令牌(防重复提交)
    session['csrf_token'] = generate_random_string()
Python

三、为什么能防御CSRF?

关键点分析:

1. 同源策略保护令牌

  • 恶意网站evil.com无法读取bank.com页面中的CSRF令牌
  • 浏览器同源策略禁止跨域读取页面内容

2. 攻击失败示例

<!-- evil.com的恶意页面 -->
<form action="https://bank.com/transfer" method="POST">
  <input type="hidden" name="amount" value="10000">
  <input type="hidden" name="to" value="hacker">
  <!-- 攻击者不知道正确的csrf_token值! -->
  <input type="hidden" name="csrf_token" value="????">
</form>
<script>document.forms[0].submit();</script>
HTML

3. 双重验证机制

  • 第一重:会话cookie(浏览器自动发送)
  • 第二重:CSRF令牌(必须存在于表单数据中)
  • 攻击者只能获取第一重,无法获取第二重

四、Flask-WTF CSRF的完整配置

from flask import Flask
from flask_wtf.csrf import CSRFProtect

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'  # 必须设置,用于签名session
app.config['WTF_CSRF_SECRET_KEY'] = 'different-secret-key'  # 可选,专门用于CSRF

csrf = CSRFProtect(app)

# AJAX请求需要额外处理
@app.after_request
def set_csrf_cookie(response):
    if request.path.startswith('/api/'):
        response.set_cookie('X-CSRFToken', csrf_token())
    return response
Python

五、AJAX请求的CSRF保护

1. 获取令牌

// 从meta标签获取(Flask-WTF自动生成)
var csrf_token = document.querySelector('meta[name="csrf-token"]').content;

// 或从cookie获取
function getCookie(name) {
    let cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        const cookies = document.cookie.split(';');
        for (let cookie of cookies) {
            cookie = cookie.trim();
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}
JavaScript

2. 发送AJAX请求

// 方法1:放在请求头中
fetch('/api/transfer', {
    method: 'POST',
    headers: {
        'X-CSRFToken': csrf_token,
        'Content-Type': 'application/json'
    },
    body: JSON.stringify(data)
});

// 方法2:放在请求体中(表单格式)
const formData = new FormData();
formData.append('csrf_token', csrf_token);
formData.append('amount', 100);
JavaScript

六、实践和注意事项

1. 安全配置

# 生产环境配置
app.config.update(
    WTF_CSRF_SSL_STRICT=True,      # 检查Referer头
    WTF_CSRF_TIME_LIMIT=3600,       # 令牌有效期
    WTF_CSRF_HEADERS=['X-CSRFToken', 'X-XSRF-Token']  # 接受的请求头
)
Python

2. 需要豁免CSRF的情况

@csrf.exempt
@app.route('/webhook', methods=['POST'])
def webhook():
    # 第三方webhook通常需要豁免
    pass
Python

3. 常见问题

  • 令牌刷新:每次验证后刷新令牌可防重复提交攻击
  • 多个标签页:Flask-WTF默认支持多标签页操作
  • API设计:纯API服务应考虑使用JWT等其他认证方式

总结

Flask-WTF的CSRF保护通过”会话绑定令牌”机制,确保:

  1. 每个会话有唯一令牌,攻击者无法猜测
  2. 令牌必须随表单提交,攻击者无法获取
  3. 双重验证机制:会话cookie + CSRF令牌

这种设计完美解决了CSRF攻击的核心问题:攻击者可以伪造请求,但无法伪造令牌。只要遵循正确的配置,就能有效保护Web应用免受跨站请求伪造攻击。

Think First

Thank for watching

The heavier the burden, the closer our lives come to the earth, the more real and truthful they become.

负担越重,我们的生命越贴近大地,它就越真切实在。

—— 《不能承受的生命之轻》米兰·昆德拉

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注