CSRF防禦方案調研結果

場景模擬

用戶在登錄B站的前提下,點擊了黑客送給它的A站的鏈接。A站的頁面中包含一個對B站資源的請求,於是用戶瀏覽器向A站發起請求,並且附帶上B站發的cookie,這就達到了冒充用戶身份實現某種操作的攻擊意圖。


類型:GET型、POST型、鏈接型

防禦辦法:

阻止不可信外域的訪問

  1. 檢驗OriginReferrer這兩個HTTP Header

在大多數情況下,瀏覽器在發送請求時會攜帶它們,並且js無法修改它們。但在少數情況下, 瀏覽器不會發送它們:
Origin值爲發起請求的頁面的URL地址(不含path即query);但IE11不會在跨站請求中加上它,另外,302重定向時所有瀏覽器也不會加它。
Referrer 在ajax、圖片和script請求中值是HTTP請求的來源地址,在頁面跳轉的情況下,是進入最終頁面的前一個跳轉頁面的地址;但在某些情況下,HTTP請求也不含Referrer,如HTTPS轉HTTP頁面,或使用含有漏洞/可修改HTTP Header的瀏覽器,或使用IE6/7的部分js API。
W3C 新修的草案裏爲Referrer Policy提供了5種值:

策略名稱 策略值(新) 對應舊值
No Referrer no-referrer never
No Referrer When Downgrade no-referrer-when-downgrade default
Origin Only (same or strict) origin origin
Origin When Cross Origin (strict) origin-when-crossorigin
Unsafe URL unsafe-url always

Referrer可在三個地方設置:

  • CSP
  • HTML頁面的<meta>標籤
  • <a>類標籤添加referrerpolicy屬性,如:<img src="http://bank.example/withdraw?amount=10000&for=hacker" referrerpolicy="no-referrer">

思考: 藉助這倆Header驗證的本質是依賴第三方(瀏覽器)的安全,但在某些特殊情況下(如用戶使用老舊瀏覽器、多級重定向跳轉等),Referrer的值不足以區分正常請求和惡意請求,此時需要其他驗證手段輔助;另外,如果瀏覽器確定可信,那也可以引入HTTP2.0新加的Sec-Fetch頭,它可以提供更加細緻的請求描述(請求對象、請求類型、請求模式) ,使用它們做驗證依據可靠性更高。P.S.當然,這麼做的前提是HTTP2.0普及,目前還需時日。

  1. 設置Cookie爲Samesite屬性

google的草案裏爲cookie添加了Samesite字段,它有兩個值StrictLax,它是兩種級別的同源限制;以鏈接跳轉爲例,當登錄Cookie的該屬性設爲Strict時,當你從百度搜索頁跳到淘寶頁,將不會自動登錄,因爲cookie沒被髮送;當值爲Lax時,在上面的情形可以自動登錄,因爲cookie可以發送。但對於在B頁面發起A域資源的請求,二者都不會攜帶該條Cookie。
在防範CSRF來說,該草案很夠用,但也存在一些弊端:

  • 當設置爲Strict時,子域名跳轉或打開新標籤重進網站,都不會攜帶Cookie。這樣的用戶體驗很差。
  • 致命問題:不支持子域,如在top.a.com下無法使用domain字段爲a.com的Cookie,這在有多個子域名時會很麻煩。

本域憑證校驗(CSRF Token)

方式一(token備份在session中):

服務器在用戶cookie的有效週期內,爲它生成一個token(通常是:hash( salt + timestamp )),並將備份存入session。如果是後端控制頁面的情況,則可以在用戶請求頁面時直接將token放入表單發給用戶,在用戶提交表單時進行驗證;在後端不可修改頁面HTML的情況,可以藉助cookie、響應頭等媒介,讓前端存儲下token,在提交表單或ajax請求時動態添加一下。在請求到達服務器後,再和session中的token比較。

方式二(token備份在cookie中):

服務器在生成token後直接並將備份寫入cookie發給前端,前端在ajax或提交表單時提取出token(因爲在CSRF攻擊中黑客無法讀取cookie的值)放在請求中(URL、請求頭、body),由於cookie也會一併發送給後端,後端在校驗時比較二者是否相同。

說明: 方式二的實施成本比方式一要低,但在大型網站上卻使用的不多。原因主要在於,爲了讓不同的子域名能夠獲取到種了token的cookie,該cookie的域必須設置成較高級別的(比如二級域名),這會給大型網站暴露出更多風險。因爲每個子域名下的js都可以修改這條cookie,倘若某個子域名存在XSS漏洞(大型網站的子域名好多的,有些使用老舊的技術開發),則黑客可以藉此突破當前域名下的CSRF防禦。

思考: 一般來說,token的生命週期和表單一致,當一個表單提交後該條token就被消耗掉,我們可以從token池中獲取並分配下一條token,這種設計常用於方式一中;當使用方式二時,因爲服務端沒有留存生成token的時間戳,不好校驗它的有效性,所以可將token的生命週期延長爲用戶登錄cookie的有效期。或者放棄使用哈希運算,換成加解密運算的方式,即可完成有效性的校驗(服務端接收到請求的時間 - 解密後獲得的生成時間),Django框架正是採用了這種方式(它自寫了一個簡單的加解密算法)。但因爲將哈希運算換成了加解密運算,無疑增加了服務器CPU的開銷。

設計案例

Spring Security

Spring Security提供了3個CsrfTokenRepository,其中HttpSessionCsrfToken和CookieCsrfToken顧明思議,用於方式一、方式二的場景,LazyCsrfToken提供了程序員自寫方法的接口。前兩種token的生成方式十分簡單:

   private String createNewToken() {
   	return UUID.randomUUID().toString();
   }

驗證邏輯在org.springframework.security.web.csrf.CsrfFilter#doFilterInternal中,流程如下:

Django

如下圖,爲Django中的CSRF驗證邏輯:

在這裏插入圖片描述

其中,csrftoken從請求的Cookie中獲取,csrfmiddlewaretoken從POST表單或請求頭中獲取,通過判斷二者解密後的secret值來決定是否爲CSRF攻擊。

參考閱讀:

  1. https://zhuanlan.zhihu.com/p/46592479
  2. https://www.jianshu.com/p/eaf4a57bbca7
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章