CSRF(Cross-Site Request Forgery)这种攻击方式在Web安全领域已经存在超过15年,但至今仍是OWASP Top 10的常客。攻击者利用用户已登录的身份,在用户不知情的情况下执行非预期的操作。想象一下这样的场景:你在浏览器登录了网银,然后不小心点开了钓鱼邮件里的链接——攻击者可能已经用你的账户完成了转账。
与XSS攻击不同,CSRF不需要注入恶意脚本。它的核心在于欺骗用户的浏览器,使其向目标网站发送精心构造的请求。这些请求会携带用户的所有cookies,就像用户自己操作的一样。我处理过的最典型的案例是:某电商平台的"一键下单"功能被滥用,攻击者构造的图片链接能让登录用户自动购买指定商品。
在深入具体方案前,我们需要建立完整的防御思维。有效的CSRF防护需要同时考虑以下维度:
下面这张对比表是我在多个项目中总结出的方案选型参考:
| 防御方案 | 实现成本 | 防护强度 | 适用场景 | 用户体验影响 |
|---|---|---|---|---|
| Token验证 | 中 | 高 | 所有表单/API | 无感知 |
| SameSite Cookie | 低 | 中 | 现代浏览器 | 可能影响跨站功能 |
| 验证码 | 高 | 极高 | 关键操作 | 操作中断 |
| 二次确认 | 中 | 高 | 资金/权限变更 | 轻度干扰 |
这是目前最主流的防御方案,我在金融项目中90%的场景都会首选它。核心原理是:服务端生成随机token,前端在提交请求时必须携带该token。
具体实现步骤:
python复制import secrets
csrf_token = secrets.token_urlsafe(32)
html复制<meta name="csrf-token" content="{{ csrf_token }}">
javascript复制// 使用axios拦截器示例
axios.interceptors.request.use(config => {
const token = document.querySelector('meta[name="csrf-token"]').content;
config.headers['X-CSRF-TOKEN'] = token;
return config;
});
python复制def verify_csrf(request):
token = request.headers.get('X-CSRF-TOKEN')
if not token or token != request.session.get('csrf_token'):
raise PermissionDenied("CSRF验证失败")
关键细节:Token需要绑定session但不建议直接存储在cookie中。我遇到过将token放在cookie导致防护失效的案例。
这个方案特别适合老系统改造,几乎零成本。通过设置cookie的SameSite属性,可以控制cookie的发送范围:
python复制response.set_cookie(
'sessionid',
value=request.session.session_key,
httponly=True,
secure=True,
samesite='Lax' # 或 'Strict'
)
三种模式的区别:
实测中发现的问题:某些广告跟踪、第三方登录功能可能会因此失效。建议在关键业务cookie上使用Strict,普通会话可用Lax。
对于资金转账、权限变更等敏感操作,我通常会强制要求二次验证。除了常规验证码,这里分享几个增强方案:
javascript复制// 检测鼠标移动轨迹是否异常
document.addEventListener('mousemove', (e) => {
storeMovementVector(e.clientX, e.clientY);
});
function isHumanOperation() {
const vectors = getStoredVectors();
return calculateEntropy(vectors) > THRESHOLD;
}
python复制def sensitive_action(request):
if time.time() - request.session['action_start_time'] < 5:
return JsonResponse({"error": "操作过快"})
这个方案适合API接口防护,原理是要求请求必须携带特定的header:
nginx复制# Nginx配置示例
location /api/ {
if ($http_x_requested_with != 'XMLHttpRequest') {
return 403;
}
}
前端需要配置:
javascript复制fetch('/api/transfer', {
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
注意:这种方法不能单独使用,需要配合其他方案。我曾在渗透测试中绕过仅依赖此机制的防护。
单一防御总有被绕过的风险。根据OWASP的建议,我总结出这些组合策略:
在某次安全审计中,我发现通过以下途径可能泄露token:
解决方案:
python复制# 动态token刷新
class CsrfRefreshMiddleware:
def process_response(self, request, response):
if request.method == 'GET':
new_token = generate_token()
request.session['csrf_token'] = new_token
response.set_cookie('csrftoken', new_token)
return response
用户同时打开多个标签页时,后生成的token会使之前页面的token失效。我的处理方案:
javascript复制// 使用BroadcastChannel监听token更新
const channel = new BroadcastChannel('csrf_update');
channel.onmessage = (event) => {
if (event.data.type === 'token_refresh') {
updateAllFormsToken(event.data.token);
}
};
高并发场景下token验证可能成为瓶颈。我的优化方案:
python复制def verify_token(request):
token = request.META.get('HTTP_X_CSRFTOKEN')
# 第一层:本地内存缓存
if token == getattr(request, '_csrf_cache', None):
return True
# 第二层:Redis集群
if redis_client.get(f'csrf:{token}'):
request._csrf_cache = token
return True
# 第三层:数据库验证
if CsrfToken.objects.filter(token=token).exists():
request._csrf_cache = token
redis_client.setex(f'csrf:{token}', 3600, 1)
return True
return False
python复制# 批量生成token池
def generate_token_pool(size=100):
return [secrets.token_urlsafe(32) for _ in range(size)]
# 中间件中循环使用
current_pool = generate_token_pool()
pool_index = 0
def get_next_token():
global pool_index
token = current_pool[pool_index % len(current_pool)]
pool_index += 1
return token
完善的防护还需要配套的检测机制:
python复制class CsrfDetectMiddleware:
def process_request(self, request):
if request.method in ('POST', 'PUT', 'DELETE'):
referer = request.META.get('HTTP_REFERER')
if referer and not is_trusted_domain(referer):
log_suspicious_request(request)
if is_high_risk_action(request.path):
request.session['needs_captcha'] = True
我在某金融项目部署的监控策略,曾成功识别出针对Token生成算法的时序预测攻击。