月度归档: 2025 年 12 月

  • 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

    Life begins on the other side of despair.

    生活在绝望的另一端开始。

    —— 萨特