場景模擬
用戶在登錄B站的前提下,點擊了黑客送給它的A站的鏈接。A站的頁面中包含一個對B站資源的請求,於是用戶瀏覽器向A站發起請求,並且附帶上B站發的cookie,這就達到了冒充用戶身份實現某種操作的攻擊意圖。
類型:GET型、POST型、鏈接型
防禦辦法:
阻止不可信外域的訪問
- 檢驗
Origin
、Referrer
這兩個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普及,目前還需時日。
- 設置Cookie爲
Samesite
屬性
google的草案裏爲cookie添加了
Samesite
字段,它有兩個值Strict
和Lax
,它是兩種級別的同源限制;以鏈接跳轉爲例,當登錄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攻擊。
參考閱讀: