一、CSRF攻击原理
什么是CSRF(跨站请求伪造)?
CSRF是一种让用户在不知情的情况下,以他们的身份执行非本意操作的攻击。
攻击流程示例:
- 用户登录银行网站
bank.com,登录后浏览器保存了会话cookie - 用户访问恶意网站
evil.com - 恶意网站包含一个自动提交的表单:
- 浏览器会自动带上
bank.com的cookie,完成转账操作
<form action="https://bank.com/transfer" method="POST">
<input type="hidden" name="amount" value="10000">
<input type="hidden" name="to" value="hacker">
</form>
<script>document.forms[0].submit();</script>HTML二、CSRF令牌防御机制
核心思想:引入一个攻击者无法获取的”秘密令牌”
Flask-WTF的实现:
1. 令牌生成
# Flask-WTF内部会为每个会话生成唯一的令牌
csrf_token = generate_random_string() # 比如:'abc123xyz'
session['csrf_token'] = csrf_token # 存储在服务器端会话中Python2. 令牌嵌入表单
<!-- 在表单中嵌入令牌 -->
<form method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<!-- csrf_token() 会从session中取出令牌 -->
<input type="text" name="amount">
</form>HTML3. 令牌验证流程
# Flask-WTF验证逻辑简化版
def validate_csrf():
# 1. 获取表单提交的令牌
form_token = request.form.get('csrf_token')
# 2. 获取服务器存储的令牌
session_token = session.get('csrf_token')
# 3. 严格比较(防时序攻击)
if not compare_digest(form_token, session_token):
abort(403) # 验证失败
# 4. 每次验证后可选:刷新令牌(防重复提交)
session['csrf_token'] = generate_random_string()Python三、为什么能防御CSRF?
关键点分析:
1. 同源策略保护令牌
- 恶意网站
evil.com无法读取bank.com页面中的CSRF令牌 - 浏览器同源策略禁止跨域读取页面内容
2. 攻击失败示例:
<!-- evil.com的恶意页面 -->
<form action="https://bank.com/transfer" method="POST">
<input type="hidden" name="amount" value="10000">
<input type="hidden" name="to" value="hacker">
<!-- 攻击者不知道正确的csrf_token值! -->
<input type="hidden" name="csrf_token" value="????">
</form>
<script>document.forms[0].submit();</script>HTML3. 双重验证机制:
- 第一重:会话cookie(浏览器自动发送)
- 第二重:CSRF令牌(必须存在于表单数据中)
- 攻击者只能获取第一重,无法获取第二重
四、Flask-WTF CSRF的完整配置
from flask import Flask
from flask_wtf.csrf import CSRFProtect
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key' # 必须设置,用于签名session
app.config['WTF_CSRF_SECRET_KEY'] = 'different-secret-key' # 可选,专门用于CSRF
csrf = CSRFProtect(app)
# AJAX请求需要额外处理
@app.after_request
def set_csrf_cookie(response):
if request.path.startswith('/api/'):
response.set_cookie('X-CSRFToken', csrf_token())
return responsePython五、AJAX请求的CSRF保护
1. 获取令牌
// 从meta标签获取(Flask-WTF自动生成)
var csrf_token = document.querySelector('meta[name="csrf-token"]').content;
// 或从cookie获取
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let cookie of cookies) {
cookie = cookie.trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}JavaScript2. 发送AJAX请求
// 方法1:放在请求头中
fetch('/api/transfer', {
method: 'POST',
headers: {
'X-CSRFToken': csrf_token,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
// 方法2:放在请求体中(表单格式)
const formData = new FormData();
formData.append('csrf_token', csrf_token);
formData.append('amount', 100);JavaScript六、实践和注意事项
1. 安全配置
# 生产环境配置
app.config.update(
WTF_CSRF_SSL_STRICT=True, # 检查Referer头
WTF_CSRF_TIME_LIMIT=3600, # 令牌有效期
WTF_CSRF_HEADERS=['X-CSRFToken', 'X-XSRF-Token'] # 接受的请求头
)Python2. 需要豁免CSRF的情况
@csrf.exempt
@app.route('/webhook', methods=['POST'])
def webhook():
# 第三方webhook通常需要豁免
passPython3. 常见问题
- 令牌刷新:每次验证后刷新令牌可防重复提交攻击
- 多个标签页:Flask-WTF默认支持多标签页操作
- API设计:纯API服务应考虑使用JWT等其他认证方式
总结
Flask-WTF的CSRF保护通过”会话绑定令牌”机制,确保:
- 每个会话有唯一令牌,攻击者无法猜测
- 令牌必须随表单提交,攻击者无法获取
- 双重验证机制:会话cookie + CSRF令牌
这种设计完美解决了CSRF攻击的核心问题:攻击者可以伪造请求,但无法伪造令牌。只要遵循正确的配置,就能有效保护Web应用免受跨站请求伪造攻击。
Think First
Thank for watching
Life begins on the other side of despair.
生活在绝望的另一端开始。
—— 萨特

