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

Become what you are.

成为你自己。

—— 尼采

评论

发表回复

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