跨站请求伪造(CSRF)攻击与防御的原理与实现
字数 1340 2025-12-13 10:36:03

跨站请求伪造(CSRF)攻击与防御的原理与实现

描述

跨站请求伪造(CSRF)是一种常见的Web安全漏洞,攻击者利用用户的登录状态,诱导用户在不知情的情况下,向目标网站发送一个恶意请求,从而执行攻击者意图的操作(如转账、修改密码等)。

核心原理:

  1. 用户已在目标网站A登录,浏览器保存了认证凭证(如Session Cookie)
  2. 用户访问了恶意网站B,B的页面中嵌入了向网站A发送请求的代码
  3. 浏览器会自动携带网站A的Cookie,网站A认为这是用户的合法请求
  4. 网站A执行了攻击者设计的操作,造成安全危害

解题过程(防御机制详解)

第一步:理解CSRF攻击流程

让我们通过一个具体场景理解攻击如何发生:

  1. 用户登录:用户登录银行网站bank.com,服务器返回Session Cookie
  2. 用户访问恶意网站:用户访问evil.com,页面包含隐藏表单:
<form action="https://bank.com/transfer" method="POST">
  <input type="hidden" name="to" value="attacker">
  <input type="hidden" name="amount" value="10000">
</form>
<script>document.forms[0].submit();</script>
  1. 自动提交请求:浏览器自动提交表单,携带bank.com的Cookie
  2. 服务器处理:银行服务器看到合法Cookie,执行转账操作
  3. 攻击成功:用户资金被盗

第二步:防御原理——CSRF令牌(CSRF Token)

最有效的防御方案是同步令牌模式

基本原理

  1. 服务器为每个用户会话生成一个唯一的、不可预测的令牌
  2. 将令牌嵌入到表单中(或通过JavaScript获取)
  3. 用户提交请求时,必须附带这个令牌
  4. 服务器验证令牌的有效性

为什么有效

  • 恶意网站无法获取这个令牌(受同源策略保护)
  • 即使攻击者能构造请求,也无法知道正确的令牌值
  • 令牌与会话绑定,每个会话不同

第三步:CSRF令牌的实现细节

1. 令牌生成与存储

# 伪代码示例
import secrets
import hashlib

def generate_csrf_token(user_session_id):
    # 生成随机字符串
    random_string = secrets.token_hex(32)
    
    # 组合会话ID和随机字符串
    token_data = f"{user_session_id}:{random_string}"
    
    # 使用HMAC签名确保完整性
    token = hashlib.sha256(token_data.encode()).hexdigest()
    
    # 服务器端存储(会话中或缓存)
    session['csrf_token'] = token
    return token

2. 令牌传递到前端

方式一:表单隐藏字段

<form action="/transfer" method="POST">
  <input type="hidden" name="csrf_token" value="{{ csrf_token }}">
  <!-- 其他表单字段 -->
</form>

方式二:HTTP Header(适用于AJAX请求)

// 从meta标签获取令牌
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;

// 设置请求头
fetch('/api/transfer', {
  method: 'POST',
  headers: {
    'X-CSRF-Token': csrfToken,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(data)
});

3. 服务器端验证

def verify_csrf_token(request):
    # 获取请求中的令牌
    request_token = request.headers.get('X-CSRF-Token') or \
                   request.form.get('csrf_token')
    
    # 获取服务器端存储的令牌
    server_token = session.get('csrf_token')
    
    if not request_token or not server_token:
        return False
    
    # 使用恒定时间比较防止时序攻击
    def constant_time_compare(val1, val2):
        """恒定时间字符串比较"""
        if len(val1) != len(val2):
            return False
        
        result = 0
        for x, y in zip(val1, val2):
            result |= ord(x) ^ ord(y)
        return result == 0
    
    return constant_time_compare(request_token, server_token)

第四步:其他防御策略

1. SameSite Cookie属性

  • 原理:限制Cookie的发送条件
  • 实现:
# 设置Cookie时
response.set_cookie('sessionid', session_id,
                   secure=True,     # 仅HTTPS
                   httponly=True,   # 禁止JavaScript访问
                   samesite='Strict')  # 严格模式
  • 三种模式:
    • Strict:完全禁止跨站发送
    • Lax:允许部分安全的跨站请求(GET请求)
    • None:允许发送,但必须配合Secure

2. 双重提交Cookie

  • 原理:令牌同时存在于Cookie和请求中
  • 实现:
// 前端从Cookie读取令牌
function getCookie(name) {
  const value = `; ${document.cookie}`;
  const parts = value.split(`; ${name}=`);
  if (parts.length === 2) return parts.pop().split(';').shift();
}

// 发送请求时
const token = getCookie('csrf_token');
fetch('/api/action', {
  headers: { 'X-CSRF-Token': token }
});

3. Referer/Origin检查

  • 原理:检查请求来源是否合法
  • 实现:
def check_referer(request):
    referer = request.headers.get('Referer')
    origin = request.headers.get('Origin')
    
    # 允许的域名列表
    allowed_domains = ['https://example.com', 'https://www.example.com']
    
    if not referer and not origin:
        return False  # 或者根据业务决定
    
    # 检查Referer或Origin
    for domain in allowed_domains:
        if referer and referer.startswith(domain):
            return True
        if origin and origin == domain:
            return True
    
    return False

第五步:实现注意事项

1. 令牌生命周期管理

  • 每个会话生成唯一令牌
  • 重要操作使用一次性令牌
  • 定期刷新令牌

2. 防御时序攻击

  • 使用恒定时间比较
  • 避免通过响应时间泄露信息

3. 处理JavaScript框架

// 在单页应用中
// 1. 首次加载时获取令牌
let csrfToken = null;

async function initializeApp() {
  const response = await fetch('/api/csrf-token');
  const data = await response.json();
  csrfToken = data.token;
  
  // 存储到内存,并设置全局请求拦截器
  axios.interceptors.request.use(config => {
    if (csrfToken && ['post', 'put', 'delete', 'patch'].includes(config.method)) {
      config.headers['X-CSRF-Token'] = csrfToken;
    }
    return config;
  });
}

4. 与CORS的配合

# 处理预检请求
if request.method == 'OPTIONS':
    response = Response()
    response.headers['Access-Control-Allow-Origin'] = 'https://trusted-site.com'
    response.headers['Access-Control-Allow-Headers'] = 'X-CSRF-Token, Content-Type'
    return response

总结

CSRF防御的核心是确保请求来自合法的源。现代Web应用通常采用组合策略:

  1. CSRF令牌:主要防御手段,确保请求携带有效令牌
  2. SameSite Cookie:浏览器级别的防御,限制Cookie发送
  3. Referer/Origin检查:辅助验证,防止令牌泄露时的攻击
  4. 重要操作二次验证:如密码、短信验证码

实现时需要考虑:

  • 令牌的生成、存储、传递、验证全过程
  • 前后端分离架构下的特殊处理
  • 与现有认证授权体系的整合
  • 性能影响(令牌验证开销)

通过综合运用这些技术,可以构建出坚固的CSRF防御体系,保护用户免受跨站请求伪造攻击。

跨站请求伪造(CSRF)攻击与防御的原理与实现 描述 跨站请求伪造(CSRF)是一种常见的Web安全漏洞,攻击者利用用户的登录状态,诱导用户在不知情的情况下,向目标网站发送一个恶意请求,从而执行攻击者意图的操作(如转账、修改密码等)。 核心原理: 用户已在目标网站A登录,浏览器保存了认证凭证(如Session Cookie) 用户访问了恶意网站B,B的页面中嵌入了向网站A发送请求的代码 浏览器会自动携带网站A的Cookie,网站A认为这是用户的合法请求 网站A执行了攻击者设计的操作,造成安全危害 解题过程(防御机制详解) 第一步:理解CSRF攻击流程 让我们通过一个具体场景理解攻击如何发生: 用户登录 :用户登录银行网站 bank.com ,服务器返回Session Cookie 用户访问恶意网站 :用户访问 evil.com ,页面包含隐藏表单: 自动提交请求 :浏览器自动提交表单,携带 bank.com 的Cookie 服务器处理 :银行服务器看到合法Cookie,执行转账操作 攻击成功 :用户资金被盗 第二步:防御原理——CSRF令牌(CSRF Token) 最有效的防御方案是 同步令牌模式 : 基本原理 : 服务器为每个用户会话生成一个唯一的、不可预测的令牌 将令牌嵌入到表单中(或通过JavaScript获取) 用户提交请求时,必须附带这个令牌 服务器验证令牌的有效性 为什么有效 : 恶意网站无法获取这个令牌(受同源策略保护) 即使攻击者能构造请求,也无法知道正确的令牌值 令牌与会话绑定,每个会话不同 第三步:CSRF令牌的实现细节 1. 令牌生成与存储 2. 令牌传递到前端 方式一:表单隐藏字段 方式二:HTTP Header (适用于AJAX请求) 3. 服务器端验证 第四步:其他防御策略 1. SameSite Cookie属性 原理:限制Cookie的发送条件 实现: 三种模式: Strict :完全禁止跨站发送 Lax :允许部分安全的跨站请求(GET请求) None :允许发送,但必须配合 Secure 2. 双重提交Cookie 原理:令牌同时存在于Cookie和请求中 实现: 3. Referer/Origin检查 原理:检查请求来源是否合法 实现: 第五步:实现注意事项 1. 令牌生命周期管理 每个会话生成唯一令牌 重要操作使用一次性令牌 定期刷新令牌 2. 防御时序攻击 使用恒定时间比较 避免通过响应时间泄露信息 3. 处理JavaScript框架 4. 与CORS的配合 总结 CSRF防御的核心是 确保请求来自合法的源 。现代Web应用通常采用组合策略: CSRF令牌 :主要防御手段,确保请求携带有效令牌 SameSite Cookie :浏览器级别的防御,限制Cookie发送 Referer/Origin检查 :辅助验证,防止令牌泄露时的攻击 重要操作二次验证 :如密码、短信验证码 实现时需要考虑: 令牌的生成、存储、传递、验证全过程 前后端分离架构下的特殊处理 与现有认证授权体系的整合 性能影响(令牌验证开销) 通过综合运用这些技术,可以构建出坚固的CSRF防御体系,保护用户免受跨站请求伪造攻击。