web安全機制問題詳解之二:CSRF

柳菁發表於2019-03-21

什麼是CSRF

CSRF(Cross-site request forgery)跨站請求偽造:攻擊者誘導受害者進入第三方網站,在第三方網站中,向被攻擊網站傳送請求。利用受害者在被攻擊網站已經獲取的註冊憑證,繞過後臺的使用者驗證,達到冒充使用者對被攻擊網站執行某項操作的目的。

一個典型的CSRF攻擊流程:

  • 使用者登入a.com,並保留了登入憑證(cookie)
  • 攻擊者引誘使用者訪問b.com,如,誘導性文字按鈕等
  • b.com向a.com傳送了一個請求:a.com/act=xx。瀏覽器會預設攜帶a.com的cookie
  • a.com接收到請求後,對請求進行驗證,並確認是受害使用者的憑證,誤以為是使用者自己傳送的請求
  • a.com以使用者名稱義執行了act=xx
  • 攻擊完成,攻擊者在使用者不知情的情況下,冒充使用者,讓a.com執行了自己定義的操作

例如:

使用者A瀏覽Gmail郵件,因好奇點選了一封“甩賣比特幣,只要998”的廣告郵件。開啟後一片空白,隨後使用者A關閉了頁面。但此時CSRF攻擊已經完成,使用者A的Gmail郵件將逐一轉發到黑客的郵箱。具體是怎麼發生的呢,關鍵在於那個空白頁面的原始碼:

<form method="POST" action="https://mail.google.com/mail/h/ewt1jmuj4ddv/?v=prf" enctype="multipart/form-data"> 
    <input type="hidden" name="cf2_emc" value="true"/> 
    <input type="hidden" name="cf2_email" value="hacker@hakermail.com"/> 
    .....
    <input type="hidden" name="irf" value="on"/> 
    <input type="hidden" name="nvp_bu_cftb" value="Create Filter"/> 
</form> 
<script> 
    document.forms[0].submit();
</script>複製程式碼

這個頁面只要一開啟就會向Gmail傳送一個post請求。請求中,執行了‘Create Filter’命令(自動轉發郵件),將所有郵件轉發到黑客郵箱hacker@hakermail.com。由於A剛登陸了Gmail,使用者登入憑證被儲存在cookie中,所以這個空白頁發出的post請求就攜帶了登陸證明,於是成功給使用者A配置了過濾器。黑客可以檢視所有使用者A的郵件,包括一些敏感資訊:郵箱驗證碼等等。此事件原型是2007年Gmail的CSRF漏洞:www.davidairey.com/google-Gmai…。目前此漏洞已修復。

幾種常見的CSRF攻擊

GET型別攻擊:http請求時,敏感引數跟在url後面,攻擊者很容易獲取併傳送包含受害者資訊的跨域請求。

POST型別攻擊:如上案列所示。

連結型別攻擊:頁面中注入惡意連結,誘導使用者點選跳轉。

防護策略

據上所示,CSRF通常是由第三方網站發起,因此可以通過增強自身網站的安全性來防禦:

  • 同源策略
  • token驗證

同源檢測

同源策略上分為兩部分:一是http請求介面的同源,還有一個是cookie的domain屬性的同源。

1. http請求,可以使用origin header、referer header確定來源域名。

在部分請求中,請求的header中會攜帶origin欄位,代表請求的域名;也有不存在origin的情況,IE11對同源的定義有別於其他瀏覽器,還有一個就是302重定向之後origin不包含在重定向的請求中,因為重定向請求是定向到新的伺服器,避免洩露origin資訊。

http請求header中還有另外一個記錄該請求來源地址的屬性referer。referer值是由瀏覽器提供的,不可排除瀏覽器自身安全性問題,和部分情況下攻擊者隱藏甚至篡改自己請求的referer。在同源策略下,可以把referer屬性設定成same-origin,對於同源的額連結和引用會傳送referer,對於跨域則不攜帶referer。

設定referer的三種方法:在csp設定;頁面頭部增設meta標籤;a標籤增加referer屬性

<a href="https://www.baidu.com/" ref="noreferrer">跳到百度</a>複製程式碼

但是,在以下情況下referer沒有或者不可信:

  • IE6、7使用window.location.href=url進行介面跳轉,會丟失referer
  • IE6、7使用window.open,也會丟失referer
  • https跳到http,所有瀏覽器都會丟失referer
  • 點選flash到達另外一個網站時,referer不可信

2. 在瀏覽器種下cookie時,若明確cookie的domain屬性(一級域名或是子域名),即僅在domain指定的域下的介面才會自動攜帶cookie,可以解決部分跨域問題,但仍不能避免攻擊者偽造的域名和網站同域(本地伺服器proxy代理請求時)。還可以設定cookie為Httponly,這樣cookie只能在請求中被攜帶,無法用document.cooke讀取、修改。

token驗證

這裡的token驗證也有兩種,一種是CSRF token的加解密校驗,還有一個是雙重Cookie驗證。

1. CSRF token

使用者開啟頁面時,伺服器會個每個使用者生成唯一的token,這個token是經過精密演算法對資料進行加密,一般包括隨機字串和時間戳。token會存在伺服器的session中。頁面提交的請求都會攜帶這個token,當伺服器拿到token後用相應解密演算法得到時間戳、及加密字串,再和伺服器session中儲存的token資訊作比較來判斷該請求的有效性,這類token一般是隨機字串。

分散式校驗

在大型網站中,使用session儲存token會帶來很大壓力。訪問單臺伺服器session是同一個。但是現在的大型網站中,我們的伺服器通常不止一臺,可能是幾十臺甚至幾百臺之多,甚至多個機房都可能在不同的省份,使用者發起的HTTP請求通常要經過像Ngnix之類的負載均衡器之後,再路由到具體的伺服器上,由於Session預設儲存在單機伺服器記憶體中,因此在分散式環境下同一個使用者傳送的多次HTTP請求可能會先後落到不同的伺服器上,導致後面發起的HTTP請求無法拿到之前的HTTP請求儲存在伺服器中的Session資料,從而使得Session機制在分散式環境下失效,因此在分散式叢集中CSRF Token需要儲存在Redis之類的公共儲存空間。

目前的解決辦法就是採用Encrypted Token Pattern方式,計算出一個結果,而不是隨機字串。這樣就無需將token儲存在session中,只需要在拿到token的時候再計算一次就行。

Token是一個比較有效的CSRF防護方法,只要頁面沒有XSS漏洞洩露Token,那麼介面的CSRF攻擊就無法成功。其次,驗證碼和密碼其實也有CSRF token的作用。

2. 雙重Cookie驗證

雙重Cookie採用以下流程:

  • 在使用者訪問網站頁面時,向請求域名注入一個Cookie,內容為隨機字串(例如csrfcookie=v8g9e4ksfhw)。
  • 在前端向後端發起請求時,取出Cookie,並新增到URL的引數中(接上例POST https://www.a.com/comment?csrfcookie=v8g9e4ksfhw)。
  • 後端介面驗證Cookie中的欄位與URL引數中的欄位是否一致,不一致則拒絕。

此方法相對於CSRF Token就簡單了許多。可以直接通過前後端攔截的的方法自動化實現。後端校驗也更加方便,只需進行請求中欄位的對比,而不需要再進行查詢和儲存Token。

當然,此方法並沒有大規模應用,其在大型網站上的安全性還是沒有CSRF Token高,原因我們舉例進行說明。

由於任何跨域都會導致前端無法獲取Cookie中的欄位(包括子域名之間),於是發生瞭如下情況:

  • 如果使用者訪問的網站為www.a.com,而後端的api域名為api.a.com。那麼在www.a.com下,前端拿不到api.a.com的Cookie,也就無法完成雙重Cookie認證。
  • 於是這個認證Cookie必須被種在a.com下,這樣每個子域都可以訪問。
  • 任何一個子域都可以修改a.com下的Cookie。
  • 某個子域名存在漏洞被XSS攻擊(例如upload.a.com)。雖然這個子域下並沒有什麼值得竊取的資訊。但攻擊者修改了a.com下的Cookie。
  • 攻擊者可以直接使用自己配置的Cookie,對XSS中招的使用者再向www.a.com下,發起CSRF攻擊。

參考:前端安全系列(二):如何防止CSRF攻擊?


相關文章