CSRF介紹
安全永遠是相對的
什麽是CSRF
CSRF(Cross Site Request Forgery, 跨站域請求偽造)是一種網絡的攻擊方式,該攻擊可以在受害者毫不知情的情況下以受害者名義偽造請求發送給受攻擊站點,從而在並未授權的情況下執行在權限保護之下的操作。簡單點說,就是攻擊者盜用了你的身份,以你的名義發送惡意請求。如以你的名義發送垃圾郵件,銀行卡轉賬等。
CSRF曾在 2007 年曾被列為互聯網 20 大安全隱患之一。其他安全隱患,比如 SQL 腳本註入,跨站域腳本攻擊等在近年來已經逐漸為眾人熟知,很多網站也都針對他們進行了防禦。然而,對於大多數人來說,CSRF 卻依然是一個陌生的概念。即便是大名鼎鼎的 Gmail, 在 2007 年底也存在著 CSRF 漏洞,從而被黑客攻擊而使 Gmail 的用戶造成巨大的損失。
CSRF原理
CSRF之所以能得逞,需具備兩個條件:
- 登錄信任站點B,並且產生cookie
- 在Cookie未失效(未登出站點B,訪問B站點的Cookie未過期,etc)的情況下,訪問攻擊站點C
攻擊站點C借助A的 cookie 取得被攻擊站點B的信任,但C並不能拿到 cookie,也看不到 cookie 的內容,當然,如果由於其他安全漏洞如XSS,盜取用戶Cookie,那麽站點C,可以繞過A,直接攻擊站點B獲取用戶信息。對於服務器返回的結果,由於瀏覽器同源策略的限制,C也無法解析。因此CSRF是修改數據,而並非盜取數據。這也解釋了為什麽我們在實現一個網站服務時,CSRF檢測,基本是對POST/PUT/DELETE進行。
CSRF防禦
CSRF的防禦,主要在服務器端驗證。常用的方法有:
- 驗證HTTP Referer字段驗證
- 驗證碼(如圖形驗證碼,隨機字符串)
- 基於Cookie帶token的驗證
- ...
Django中CSRF驗證實現原理和應用
Lucommon框架基於rest framework和Django二次封裝。默認支持CSRF檢測驗證。在Brood,irelease2,QC2項目陸續接入SSO用戶登錄認證後,原來正常工作的POST/DELETE接口,報出CSRF驗證失敗的問題。對於Django這類成熟的框架,解決辦法相對也簡單。本節,深入到Django和Rest framework的csrf模塊的源代碼,對Django中CSRF檢測原理進行剖析。
Django中CSRF檢測,采用基於Cookie帶token驗證方式實現。
Rest Framework強制CSRF驗證
在項目settings.py文件裏,去掉中間件:'django.middleware.csrf.CsrfViewMiddleware',會發現請求還是報CSRF驗證失敗。原因是,rest framework基於django,而rest framework框架默認是強制進行CSRF驗證的:
rest_framework/authentication.py
class SessionAuthentication(BaseAuthentication):
"""
Use Django's session framework for authentication.
"""
def authenticate(self, request):
"""
Returns a `User` if the request session currently has a logged in user.
Otherwise returns `None`.
"""
# Get the session-based user from the underlying HttpRequest object
user = getattr(request._request, 'user', None)
# Unauthenticated, CSRF validation not required
if not user or not user.is_active:
return None
self.enforce_csrf(request)
# CSRF passed with authenticated user
return (user, None)
def enforce_csrf(self, request):
"""
Enforce CSRF validation for session based authentication.
"""
reason = CSRFCheck().process_view(request, None, (), {})
if reason:
# CSRF failed, bail with explicit error message
raise exceptions.PermissionDenied('CSRF Failed: %s' % reason)
Django中CSRF驗證實現代碼中調用self.enforce_csrf(request)強制加入CSRF檢測。在檢測函數裏,CSRFCheck實際是調用django框架的中間件進行CSRF檢測。因此當我們項目接入SSO進行用戶登錄驗證後,基於SessionAuthentication的模塊被激活,原來POST/DELET/PUT相關接口,就報錯了。下面我們看看django的CSRF檢測模塊實現部分。
代碼位於django/middleware/csrf.py裏,在進入視圖函數前,process_view函數裏,驗證主步驟分三步走:
第一步:獲取cookie裏的CSRFtoken
提取Cookie裏的CSRF token
def process_view(self, request, callback, callback_args, callback_kwargs):
try:
csrf_token = _sanitize_token(
request.COOKIES[settings.CSRF_COOKIE_NAME])
# Use same token next time
request.META['CSRF_COOKIE'] = csrf_token
except KeyError:
csrf_token = None
# Generate token and store it in the request, so it's
# available to the view.
request.META["CSRF_COOKIE"] = _get_new_csrf_key()
顯然,Django對於POST和DELETE/PUT待比對的csrf token提取不一樣:第二步:提取待比對的token信息(POST不同於PUT/DELETE)
- POST:從表單裏獲取key為csrfmiddlewaretoken的值作為待比對的csrf token
- PUT/DELETE:從頭部(Header)獲取key為X-CSRFToken作為待比對的csrf token
- GET/HEAD/OPTIONS/TRACE:不做CSRF驗證
提取待比對的CSRF token
request_csrf_token = ""
if request.method == "POST":
request_csrf_token = request.POST.get('csrfmiddlewaretoken', '')
if request_csrf_token == "":
# Fall back to X-CSRFToken, to make things easier for AJAX,
# and possible for PUT/DELETE.
request_csrf_token = request.META.get('HTTP_X_CSRFTOKEN', '')
將第一步中cookie裏的CSRF token於第二步中的待比對的CSRF token匹配:第三步:匹配CSRF token
匹配CSRF token
if not constant_time_compare(request_csrf_token, csrf_token):
return self._reject(request, REASON_BAD_TOKEN)
return self._accept(request)
CSRF驗證前端應用邏輯看到這裏,我們已經看清楚了Django裏CSRF token驗證實現邏輯。Django裏csrf模塊,還有其他的功能,沒有一一介紹,比如服務器端Response中設置CSRF token等。
看完Django中CSRF實現後,可以非常快速的針對不同類型的接口(POST/PUT/DELETE)編寫前端邏輯,正確的輸入CSRF驗證需要的信息。
POST CSRF輸入
- 如果是用Django的模板來渲染HTML,只需要在表單部分,輸入csrf tag即可,如
Post csrf輸入
<form action="" method="POST">
{% csrf_token %}
<input name="name" id="name" title="Username" value="https://my.oschina.net/hjh188/blog/hujunhui617" />
</form>
- 如果不是用Django模板渲染HTML,則需要加入一行隱藏的input:
Post csrf輸入
<form action="" method="POST">
<input type="hidden" name="csrfmiddlewaretoken" value="https://my.oschina.net/hjh188/blog/p6kfZJjlH7CJeQQy1EP51Xv7WIJcTQRW" />
<input name="name" id="name" title="Username" value="https://my.oschina.net/hjh188/blog/hujunhui617" />
</form>
PUT/DELETE CSRF輸入{% csrf_token %}實際跟第二種情況一致,Django在模板渲染的時候,會自動生成相應的隱藏input標簽
PUT/DELETE跟POST不一樣,就像註釋代碼說的,為了更加方便使用。PUT/DELETE時,只需要在HTTP請求頭,加入X-CSRFToken即可,如:
最後需要註意的一點是,不論POST,還是PUT/DELETE,待比對的CSRF token保持跟Cookie一致。
Lucommon裏CSRFDisable中間件
lucommon基於Rest framework框架,加入了CSRF驗證的開關設置(DISABLE_CSRF_CHECK),後臺可以選擇是否進行CSRF驗證。
lucommon這部分的實現,非常簡單,因為看清楚了Django CSRF的實現,lucommon只需要實現一個Disable CSRF的中間件即可:
lucommon LuDisableCSRF
class LuDisableCSRF(object):
"""
Disable CSRF check
"""
def process_request(self, request):
setattr(request, '_dont_enforce_csrf_checks', True)
Tags: 銀行卡 黑客攻擊 Request 瀏覽器 cookie
文章來源: