跨站请求伪造(CSRF)攻击与防御的原理与实现
字数 1340 2025-12-13 10:36:03
跨站请求伪造(CSRF)攻击与防御的原理与实现
描述
跨站请求伪造(CSRF)是一种常见的Web安全漏洞,攻击者利用用户的登录状态,诱导用户在不知情的情况下,向目标网站发送一个恶意请求,从而执行攻击者意图的操作(如转账、修改密码等)。
核心原理:
- 用户已在目标网站A登录,浏览器保存了认证凭证(如Session Cookie)
- 用户访问了恶意网站B,B的页面中嵌入了向网站A发送请求的代码
- 浏览器会自动携带网站A的Cookie,网站A认为这是用户的合法请求
- 网站A执行了攻击者设计的操作,造成安全危害
解题过程(防御机制详解)
第一步:理解CSRF攻击流程
让我们通过一个具体场景理解攻击如何发生:
- 用户登录:用户登录银行网站
bank.com,服务器返回Session Cookie - 用户访问恶意网站:用户访问
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>
- 自动提交请求:浏览器自动提交表单,携带
bank.com的Cookie - 服务器处理:银行服务器看到合法Cookie,执行转账操作
- 攻击成功:用户资金被盗
第二步:防御原理——CSRF令牌(CSRF Token)
最有效的防御方案是同步令牌模式:
基本原理:
- 服务器为每个用户会话生成一个唯一的、不可预测的令牌
- 将令牌嵌入到表单中(或通过JavaScript获取)
- 用户提交请求时,必须附带这个令牌
- 服务器验证令牌的有效性
为什么有效:
- 恶意网站无法获取这个令牌(受同源策略保护)
- 即使攻击者能构造请求,也无法知道正确的令牌值
- 令牌与会话绑定,每个会话不同
第三步: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应用通常采用组合策略:
- CSRF令牌:主要防御手段,确保请求携带有效令牌
- SameSite Cookie:浏览器级别的防御,限制Cookie发送
- Referer/Origin检查:辅助验证,防止令牌泄露时的攻击
- 重要操作二次验证:如密码、短信验证码
实现时需要考虑:
- 令牌的生成、存储、传递、验证全过程
- 前后端分离架构下的特殊处理
- 与现有认证授权体系的整合
- 性能影响(令牌验证开销)
通过综合运用这些技术,可以构建出坚固的CSRF防御体系,保护用户免受跨站请求伪造攻击。