一、CSRF 攻擊
1、CSRF漏洞的發生
相比 XSS,CSRF 的名氣似乎並不是那么大,很多人都認為CSRF“不那么有破壞性”。真的是這樣嗎?接下來有請小明出場 —— 小明的悲慘遭遇:
這一天,小明同學百無聊賴地刷着Gmail郵件。大部分都是沒營養的通知、驗證碼、聊天記錄之類。但有一封郵件引起了小明的注意:“甩賣比特幣,一個只要998!!”
聰明的小明當然知道這種肯定是騙子,但還是抱着好奇的態度點了進去(請勿模仿)。果然,這只是一個什么都沒有的空白頁面,小明失望的關閉了頁面。一切似乎什么都沒有發生……
在這平靜的外表之下,黑客的攻擊已然得手。小明的Gmail中,被偷偷設置了一個過濾規則,這個規則使得所有的郵件都會被自動轉發到hacker@hackermail.com。小明還在繼續刷着郵件,殊不知他的郵件正在一封封地,如脫韁的野馬一般地,持續不斷地向着黑客的郵箱轉發而去。
不久之后的一天,小明發現自己的域名已經被轉讓了。懵懂的小明以為是域名到期自己忘了續費,直到有一天,對方開出了 $650 的贖回價碼,小明才開始覺得不太對勁。
小明仔細查了下域名的轉讓,對方是擁有自己的驗證碼的,而域名的驗證碼只存在於自己的郵箱里面。小明回想起那天奇怪的鏈接,打開后重新查看了“空白頁”的源碼:
<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@hackermail.com”。
小明由於剛剛就登陸了Gmail,所以這個請求發送時,攜帶着小明的登錄憑證(Cookie),Gmail的后台接收到請求,驗證了確實有小明的登錄憑證,於是成功給小明配置了過濾器。
黑客可以查看小明的所有郵件,包括郵件里的域名驗證碼等隱私信息。拿到驗證碼之后,黑客就可以要求域名服務商把域名重置給自己。
小明很快打開Gmail,找到了那條過濾器,將其刪除。然而,已經泄露的郵件,已經被轉讓的域名,再也無法挽回了……
以上就是小明的悲慘遭遇。而“點開一個黑客的鏈接,所有郵件都被竊取”這種事情並不是杜撰的,此事件原型是2007年Gmail的CSRF漏洞:https://www.davidairey.com/google-Gmail-security-hijack/
當然,目前此漏洞已被Gmail修復,請使用Gmail的同學不要慌張。
二、什么是 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執行了自己定義的操作。
三、幾種常見的攻擊類型
1、GET類型的 CSRF
GET類型的CSRF利用非常簡單,只需要一個HTTP請求,一般會這樣利用:

利用 img 標簽自帶跨域屬性,在受害者訪問含有這個img的頁面后,瀏覽器會自動向http://bank.example/withdraw?account=xiaoming&amount=10000&for=hacker
發出一次HTTP請求。bank.example就會收到包含受害者登錄信息的一次跨域請求。
2、POST類型的CSRF
這種類型的CSRF利用起來通常使用的是一個自動提交的表單,如:
<form action="http://bank.example/withdraw" method=POST>
<input type="hidden" name="account" value="xiaoming" />
<input type="hidden" name="amount" value="10000" />
<input type="hidden" name="for" value="hacker" />
</form>
<script> document.forms[0].submit(); </script>
訪問該頁面后,表單會自動提交,相當於模擬用戶完成了一次POST操作。
POST類型的攻擊通常比GET要求更加嚴格一點,但仍並不復雜。任何個人網站、博客,被黑客上傳頁面的網站都有可能是發起攻擊的來源,后端接口不能將安全寄托在僅允許POST上面。
3、鏈接類型的CSRF
鏈接類型的CSRF並不常見,比起其他兩種用戶打開頁面就中招的情況,這種需要用戶點擊鏈接才會觸發。這種類型通常是在論壇中發布的圖片中嵌入惡意鏈接,或者以廣告的形式誘導用戶中招,攻擊者通常會以比較誇張的詞語誘騙用戶點擊,例如:
<a href="http://test.com/csrf/withdraw.php?amount=1000&for=hacker" taget="_blank">重磅消息!!<a/>
由於之前用戶登錄了信任的網站A,並且保存登錄狀態,只要用戶主動訪問上面的這個PHP頁面,則表示攻擊成功。
四、CSRF 的特點
- 攻擊一般發起在第三方網站,而不是被攻擊的網站。被攻擊的網站無法防止攻擊發生。
- 攻擊利用受害者在被攻擊網站的登錄憑證,冒充受害者提交操作;而不是直接竊取數據。
- 整個過程攻擊者並不能獲取到受害者的登錄憑證,僅僅是“冒用”。
- 跨站請求可以用各種方式:圖片URL、超鏈接、CORS、Form提交等等。部分請求方式可以直接嵌入在第三方論壇、文章中,難以進行追蹤。
CSRF 通常是跨域的,因為外域通常更容易被攻擊者掌控。但是如果本域下有容易被利用的功能,比如可以發圖和鏈接的論壇和評論區,攻擊可以直接在本域下進行,而且這種攻擊更加危險。
五、防護策略
CSRF 通常從第三方網站發起,被攻擊的網站無法防止攻擊發生,只能通過增強自己網站針對CSRF的防護能力來提升安全性。上面講了CSRF的兩個特點:
- CSRF(通常)發生在第三方域名。
- CSRF攻擊者不能獲取到Cookie等信息,只是使用。
針對這兩點,我們可以專門制定防護策略,如下:
1、阻止不明外域的訪問:(1)同源檢測;(2)Samesite Cookie。
2、提交時要求附加本域才能獲取的信息:(1)CSRF Token;(2)雙重Cookie驗證。
以下我們對各種防護方法做詳細說明。
六、同源檢測
既然 CSRF 大多來自第三方網站,那么我們就直接禁止外域(或者不受信任的域名)對我們發起請求。
那么問題來了,我們如何判斷請求是否來自外域呢?在HTTP協議中,每一個異步請求都會攜帶兩個Header,用於標記來源域名:
- Origin Header
- Referer Header
這兩個Header在瀏覽器發起請求時,大多數情況會自動帶上,並且不能由前端自定義內容。 服務器可以通過解析這兩個Header中的域名,確定請求的來源域。
1、使用 Origin Header 確定來源域名
在部分與CSRF有關的請求中,請求的Header中會攜帶Origin字段。字段內包含請求的域名(不包含path及query)。如果Origin存在,那么直接使用Origin中的字段確認來源域名就可以。但是Origin在以下兩種情況下並不存在:
(1)IE11同源策略: IE 11 不會在跨站CORS請求上添加Origin標頭,Referer頭將仍然是唯一的標識。最根本原因是因為IE 11對同源的定義和其他瀏覽器有不同,有兩個主要的區別,可以參考MDN Same-origin_policy#IE_Exceptions
(2)302重定向: 在302重定向之后Origin不包含在重定向的請求中,因為Origin可能會被認為是其他來源的敏感信息。對於302重定向的情況來說都是定向到新的服務器上的URL,因此瀏覽器不想將Origin泄漏到新的服務器上。
2、使用 Referer Header 確定來源域名
根據HTTP協議,在HTTP頭中有一個字段叫Referer,記錄了該HTTP請求的來源地址。 對於Ajax請求,圖片和script等資源請求,Referer為發起請求的頁面地址。對於頁面跳轉,Referer為打開頁面歷史記錄的前一個頁面地址。因此我們使用Referer中鏈接的Origin部分可以得知請求的來源域名。
這種方法並非萬無一失,Referer的值是由瀏覽器提供的,雖然HTTP協議上有明確的要求,但是每個瀏覽器對於Referer的具體實現可能有差別,並不能保證瀏覽器自身沒有安全漏洞。使用驗證 Referer 值的方法,就是把安全性都依賴於第三方(即瀏覽器)來保障,從理論上來講,這樣並不是很安全。在部分情況下,攻擊者可以隱藏,甚至修改自己請求的Referer。
但是 referer 並不可信,以及有些情況會丟失 referer。
3、無法確認來源域名情況
當Origin和Referer頭文件不存在時該怎么辦?如果Origin和Referer都不存在,建議直接進行阻止,特別是如果您沒有使用隨機CSRF Token(參考下方)作為第二次檢查。
綜上所述:同源驗證是一個相對簡單的防范方法,能夠防范絕大多數的CSRF攻擊。但這並不是萬無一失的,對於安全性要求較高,或者有較多用戶輸入內容的網站,我們就要對關鍵的接口做額外的防護措施。
七、CSRF Token
前面講到CSRF的另一個特征是,攻擊者無法直接竊取到用戶的信息(Cookie,Header,網站內容等),僅僅是冒用Cookie中的信息。
而CSRF攻擊之所以能夠成功,是因為服務器誤把攻擊者發送的請求當成了用戶自己的請求。那么我們可以要求所有的用戶請求都攜帶一個CSRF攻擊者無法獲取到的Token。服務器通過校驗請求是否攜帶正確的Token,來把正常的請求和攻擊的請求區分開,也可以防范CSRF的攻擊。
原理:CSRF Token的防護策略分為三個步驟:
1、將CSRF Token輸出到頁面中
2、頁面提交的請求攜帶這個Token
3、服務器驗證Token是否正確
總結:Token是一個比較有效的CSRF防護方法,只要頁面沒有XSS漏洞泄露Token,那么接口的CSRF攻擊就無法成功。但是此方法的實現比較復雜,需要給每一個頁面都寫入Token(前端無法使用純靜態頁面),每一個Form及Ajax請求都攜帶這個Token,后端對每一個接口都進行校驗,並保證頁面Token及請求Token一致。這就使得這個防護策略不能在通用的攔截上統一攔截處理,而需要每一個頁面和接口都添加對應的輸出和校驗。這種方法工作量巨大,且有可能遺漏。
驗證碼和密碼其實也可以起到CSRF Token的作用哦,而且更安全。
為什么很多銀行等網站會要求已經登錄的用戶在轉賬時再次輸入密碼,現在是不是有一定道理了?
八、雙重Cookie驗證
在會話中存儲CSRF Token比較繁瑣,而且不能在通用的攔截上統一處理所有的接口。那么另一種防御措施是使用雙重提交Cookie。利用CSRF攻擊不能獲取到用戶Cookie的特點,我們可以要求Ajax和表單請求攜帶一個Cookie中的值。
雙重Cookie采用以下流程:
(1)在用戶訪問網站頁面時,向請求域名注入一個Cookie,內容為隨機字符串(例如csrfcookie=v8g9e4ksfhw
)。
(2)在前端向后端發起請求時,取出Cookie,並添加到URL的參數中(接上例POST https://www.a.com/comment?csrfcookie=v8g9e4ksfhw
)。
(3)后端接口驗證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攻擊。
總結:
(1)用雙重Cookie防御CSRF的優點:
- 無需使用Session,適用面更廣,易於實施。
- Token儲存於客戶端中,不會給服務器帶來壓力。
- 相對於Token,實施成本更低,可以在前后端統一攔截校驗,而不需要一個個接口和頁面添加。
(2)缺點:
- Cookie中增加了額外的字段。
- 如果有其他漏洞(例如XSS),攻擊者可以注入Cookie,那么該防御方式失效。
- 難以做到子域名的隔離。
- 為了確保Cookie傳輸安全,采用這種防御方式的最好確保用整站HTTPS的方式,如果還沒切HTTPS的使用這種方式也會有風險。
九、Samesite Cookie屬性
防止CSRF攻擊的辦法已經有上面的預防措施。為了從源頭上解決這個問題,Google起草了一份草案來改進HTTP協議,那就是為Set-Cookie響應頭新增Samesite屬性,它用來標明這個 Cookie是個“同站 Cookie”,同站Cookie只能作為第一方Cookie,不能作為第三方Cookie,Samesite 有兩個屬性值,分別是 Strict 和 Lax,下面分別講解:
1、Samesite=Strict:這種稱為嚴格模式,表明這個 Cookie 在任何情況下都不可能作為第三方 Cookie,絕無例外。比如說 b.com 設置了如下 Cookie:
Set-Cookie: foo=1; Samesite=Strict Set-Cookie: bar=2; Samesite=Lax Set-Cookie: baz=3
我們在 a.com 下發起對 b.com 的任意請求,foo 這個 Cookie 都不會被包含在 Cookie 請求頭中,但 bar 會。舉個實際的例子就是,假如淘寶網站用來識別用戶登錄與否的 Cookie 被設置成了 Samesite=Strict,那么用戶從百度搜索頁面甚至天貓頁面的鏈接點擊進入淘寶后,淘寶都不會是登錄狀態,因為淘寶的服務器不會接受到那個 Cookie,其它網站發起的對淘寶的任意請求都不會帶上那個 Cookie。
2、Samesite=Lax:這種稱為寬松模式,比 Strict 放寬了點限制:假如這個請求是這種請求(改變了當前頁面或者打開了新頁面)且同時是個GET請求,則這個Cookie可以作為第三方Cookie。
比如上面例子繼續:當用戶從 a.com 點擊鏈接進入 b.com 時,foo 這個 Cookie 不會被包含在 Cookie 請求頭中,但 bar 和 baz 會,也就是說用戶在不同網站之間通過鏈接跳轉是不受影響了。但假如這個請求是從 a.com 發起的對 b.com 的異步請求,或者頁面跳轉是通過表單的 post 提交觸發的,則bar也不會發送。
生成Token放到Cookie中並且設置Cookie的Samesite,Java代碼如下:
private void addTokenCookieAndHeader(HttpServletRequest httpRequest, HttpServletResponse httpResponse) { //生成token
String sToken = this.generateToken(); //手動添加Cookie實現支持“Samesite=strict” //Cookie添加雙重驗證
String CookieSpec = String.format("%s=%s; Path=%s; HttpOnly; Samesite=Strict", this.determineCookieName(httpRequest), sToken, httpRequest.getRequestURI()); httpResponse.addHeader("Set-Cookie", CookieSpec); httpResponse.setHeader(CSRF_TOKEN_NAME, token); }
3、我們應該如何使用SamesiteCookie
如果SamesiteCookie被設置為Strict,瀏覽器在任何跨域請求中都不會攜帶Cookie,新標簽重新打開也不攜帶,所以說CSRF攻擊基本沒有機會。
但是跳轉子域名或者是新標簽重新打開剛登陸的網站,之前的Cookie都不會存在。尤其是有登錄的網站,那么我們新打開一個標簽進入,或者跳轉到子域名的網站,都需要重新登錄。對於用戶來講,可能體驗不會很好。
如果SamesiteCookie被設置為Lax,那么其他網站通過頁面跳轉過來的時候可以使用Cookie,可以保障外域連接打開頁面時用戶的登錄狀態。但相應的,其安全性也比較低。
另外一個問題是Samesite的兼容性不是很好,現階段除了從新版Chrome和Firefox支持以外,Safari以及iOS Safari都還不支持,現階段看來暫時還不能普及。
而且,SamesiteCookie目前有一個致命的缺陷:不支持子域。例如,種在topic.a.com下的Cookie,並不能使用a.com下種植的SamesiteCookie。這就導致了當我們網站有多個子域名時,不能使用SamesiteCookie在主域名存儲用戶登錄信息。每個子域名都需要用戶重新登錄一次。
總之,SamesiteCookie是一個可能替代同源驗證的方案,但目前還並不成熟,其應用場景有待觀望。
十、防止網站被利用
前面所說的,都是被攻擊的網站如何做好防護,而非防止攻擊的發生,CSRF的攻擊可以來自:
- 攻擊者自己的網站。
- 有文件上傳漏洞的網站。
- 第三方論壇等用戶內容。
- 被攻擊網站自己的評論功能等。
對於來自黑客自己的網站,我們無法防護。但對其他情況,那么如何防止自己的網站被利用成為攻擊的源頭呢?
- 嚴格管理所有的上傳接口,防止任何預期之外的上傳內容(例如HTML)。
- 添加Header
X-Content-Type-Options: nosniff
防止黑客上傳HTML內容的資源(例如圖片)被解析為網頁。 - 對於用戶上傳的圖片,進行轉存或者校驗。不要直接使用用戶填寫的圖片鏈接。
- 當前用戶打開其他用戶填寫的鏈接時,需告知風險(這也是很多論壇不允許直接在內容中發布外域鏈接的原因之一,不僅僅是為了用戶留存,也有安全考慮)。
總結
簡單總結一下上文的防護策略:
1、CSRF 自動防御策略:同源檢測(Origin 和 Referer 驗證)。
2、CSRF主動防御措施:CSRF_Token驗證 或者 雙重Cookie驗證 以及配合Samesite Cookie。
3、保證頁面的冪等性,后端接口不要在GET頁面中做用戶操作。
為了更好的防御CSRF,最佳實踐應該是結合上面總結的防御措施方式中的優缺點來綜合考慮,結合當前Web應用程序自身的情況做合適的選擇,才能更好的預防CSRF的發生。
學習原文鏈接:https://tech.meituan.com/2018/10/11/fe-security-csrf.html