1. 什么是CSRF攻击 CSRF(Cross-Site Request Forgery)是一种Web安全漏洞,攻击者诱导用户在已登录的网站上执行非预期的操作。
1 2 3 4 5 6 <form action ="https://bank.com/transfer" method ="POST" style ="display:none;" > <input name ="to" value ="attacker@evil.com" > <input name ="amount" value ="10000" > </form > <script > document .forms [0 ].submit ();</script >
如果用户在银行网站已登录,这个表单会自动提交,转账给攻击者。
2. Django的CSRF保护机制 2.1 双重Token验证 1 2 3 4 5 6 7 8 csrf_secret = _get_new_csrf_string() request.META["CSRF_COOKIE" ] = _salt_cipher_secret(csrf_secret)def get_token (request ): csrf_secret = _unsalt_cipher_token(request.META["CSRF_COOKIE" ]) return _salt_cipher_secret(csrf_secret)
2.2 Salt加密机制 1 2 3 4 5 6 7 def _salt_cipher_secret (secret ): """使用salt对密钥进行加密""" salt = _get_new_csrf_string() chars = CSRF_ALLOWED_CHARS pairs = zip ((chars.index(x) for x in secret), (chars.index(x) for x in salt)) cipher = '' .join(chars[(x + y) % len (chars)] for x, y in pairs) return salt + cipher
加密过程 :
生成32位随机salt
将secret和salt对应字符的ASCII码相加
结果对字符集长度取模,得到新字符
返回 salt + 加密结果(总长64位)
3. 核心验证流程 3.1 process_view方法 - 主要验证逻辑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def process_view (self, request, callback, callback_args, callback_kwargs ): if getattr (request, 'csrf_processing_done' , False ): return None csrf_token = self ._get_token(request) if getattr (callback, 'csrf_exempt' , False ): return None if request.method not in ('GET' , 'HEAD' , 'OPTIONS' , 'TRACE' ):
3.2 HTTPS下的Referer检查 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 if request.is_secure(): referer = force_text(request.META.get('HTTP_REFERER' )) if referer is None : return self ._reject(request, REASON_NO_REFERER) referer = urlparse(referer) if '' in (referer.scheme, referer.netloc): return self ._reject(request, REASON_MALFORMED_REFERER) if referer.scheme != 'https' : return self ._reject(request, REASON_INSECURE_REFERER) good_hosts = list (settings.CSRF_TRUSTED_ORIGINS) good_hosts.append(request.get_host()) if not any (is_same_domain(referer.netloc, host) for host in good_hosts): return self ._reject(request, REASON_BAD_REFERER % referer.geturl())
为什么HTTPS需要Referer检查?
在HTTPS环境下,攻击者可能通过中间人攻击绕过CSRF保护。Referer检查确保请求来源可信。
3.3 Token验证 1 2 3 4 5 6 7 8 9 10 11 request_csrf_token = request.POST.get('csrfmiddlewaretoken' , '' )if request_csrf_token == "" : request_csrf_token = request.META.get(settings.CSRF_HEADER_NAME, '' ) request_csrf_token = _sanitize_token(request_csrf_token)if not _compare_salted_tokens(request_csrf_token, csrf_token): return self ._reject(request, REASON_BAD_TOKEN)
4. Token比较机制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def _compare_salted_tokens (request_csrf_token, csrf_token ): """比较两个加盐的token""" return constant_time_compare( _unsalt_cipher_token(request_csrf_token), _unsalt_cipher_token(csrf_token), )def _unsalt_cipher_token (token ): """解密token获取原始secret""" salt = token[:CSRF_SECRET_LENGTH] token = token[CSRF_SECRET_LENGTH:] chars = CSRF_ALLOWED_CHARS pairs = zip ((chars.index(x) for x in token), (chars.index(x) for x in salt)) secret = '' .join(chars[x - y] for x, y in pairs) return secret
5. Cookie管理 5.1 设置CSRF Cookie 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def _set_token (self, request, response ): if settings.CSRF_USE_SESSIONS: request.session[CSRF_SESSION_KEY] = request.META['CSRF_COOKIE' ] else : response.set_cookie( settings.CSRF_COOKIE_NAME, request.META['CSRF_COOKIE' ], max_age=settings.CSRF_COOKIE_AGE, domain=settings.CSRF_COOKIE_DOMAIN, path=settings.CSRF_COOKIE_PATH, secure=settings.CSRF_COOKIE_SECURE, httponly=settings.CSRF_COOKIE_HTTPONLY, )
5.2 process_response - Cookie更新 1 2 3 4 5 6 7 8 9 10 11 12 13 14 def process_response (self, request, response ): if not getattr (request, 'csrf_cookie_needs_reset' , False ): if getattr (response, 'csrf_cookie_set' , False ): return response if not request.META.get("CSRF_COOKIE_USED" , False ): return response self ._set_token(request, response) response.csrf_cookie_set = True return response
6. 实际使用示例 6.1 模板中使用 1 2 3 4 5 6 7 8 9 10 11 12 13 <form method ="post" > {% csrf_token %} <input name ="username" value ="john" > <button type ="submit" > 提交</button > </form > <form method ="post" > <input type ="hidden" name ="csrfmiddlewaretoken" value ="abc123...xyz789" > <input name ="username" value ="john" > <button type ="submit" > 提交</button > </form >
6.2 AJAX请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 function getCSRFToken ( ) { return document .querySelector ('[name=csrfmiddlewaretoken]' ).value ; }fetch ('/api/data/' , { method : 'POST' , headers : { 'X-CSRFToken' : getCSRFToken (), 'Content-Type' : 'application/json' , }, body : JSON .stringify ({data : 'value' }) });
7. 配置选项 1 2 3 4 5 6 7 8 9 10 11 12 13 CSRF_COOKIE_NAME = 'csrftoken' CSRF_COOKIE_AGE = None CSRF_COOKIE_DOMAIN = None CSRF_COOKIE_PATH = '/' CSRF_COOKIE_SECURE = False CSRF_COOKIE_HTTPONLY = False CSRF_COOKIE_SAMESITE = 'Lax' CSRF_USE_SESSIONS = False CSRF_TRUSTED_ORIGINS = [] CSRF_HEADER_NAME = 'HTTP_X_CSRFTOKEN' CSRF_FAILURE_VIEW = 'django.views.csrf.csrf_failure'
8. 安全特性总结
双重验证 :Cookie + 表单Token
Salt加密 :每次生成不同的Token
Referer检查 :HTTPS下验证请求来源
时序攻击防护 :使用constant_time_compare
Session隔离 :每个用户独立的CSRF密钥
域名限制 :通过配置控制可信来源
这个中间件提供了完整的CSRF保护,有效防止跨站请求伪造攻击,是Django安全体系的重要组成部分。