最近在維護一些老項目,調試時發現請求屢屢被拒絕,仔細看了一下項目的源碼,發現有csrf token校驗,借這個機會把csrf攻擊學習了一下,總結成文。本文主要總結什麼是csrf攻擊以及有哪些方法來防範,接下來會再寫一篇文章,從源碼中來學習一下實戰中是如何防護csrf攻擊的。php
主要內容以下:前端
什麼是CSRF攻擊程序員
CSRF的特色後端
防禦策略api
總結跨域
CSRF(Cross-site request forgery)跨站請求僞造:攻擊者誘導受害者進入第三方網站,在第三方網站中,向被攻擊網站發送跨站請求。利用受害者在被攻擊網站已經獲取的註冊憑證(好比cookie),繞事後臺的用戶驗證,達到冒充用戶對被攻擊的網站執行某項操做的目的。瀏覽器
一個典型的CSRF攻擊有着以下的流程:安全
GET類型的CSRF利用很是簡單,只須要一個HTTP,通常會這樣利用:服務器
<img src="http://bank.example/withdraw?account&=xx&amount=100&to=hacker" >
在受害者訪問含有這個img的頁面以後,瀏覽器會自動向http://bank.example/withdraw?account=xx&amount=100&to=hacker發送一次HTTP請求。bank.example就會收到包含受害者登陸信息的一次跨域請求。
這種類型的CSRF利用起來一般使用的是一個自動提交的表單,如:
<form action = "https://bank.example/withdraw" method="POST"> <input type="hidden" name="account" value="xx" /> <input type="hidden" name="ammount" value="100" /> <input type="hidden" name="for" value="hacker" /> </form> <script> document.forms[0].submit(); </script>
訪問該頁面後,表單會自動提交,至關於模擬用戶完成了一次POST操做。
POST類型的攻擊一般比GET要求更加嚴格一點,但並不複雜。任何我的網站、博客,被黑客上傳頁面的網站都有多是發起攻擊的來源,後端接口不能將安全寄託在僅容許POST上面。
連接類型的CSRF並不常見,比起其餘兩種用戶打開頁面就會中招的狀況,這種須要用戶點擊連接纔會觸發。這種類型一般是在論壇中發佈圖片中嵌入惡意連接,或者以廣告的形式誘導用戶中招,攻擊者一般會以比較誇張的詞語誘騙用戶點擊,例如:
<a href="https://bank.example.com/csrf/withdraw.php?amount=100&to=hacker" target="_blank">
文章和馬伊琍離婚!!!
</a>
因爲以前用戶已經登陸,在登陸狀態還未過時時,只要用戶主動訪問上面的這個PHP頁面,則攻擊成功。
CSRF一般是跨域的,由於外域一般更容易被攻擊者掌控。可是若是本域下有容易被利用的功能,好比能夠發圖和連接的論壇和評論區,攻擊能夠直接在本域下進行,並且這種攻擊更加危險。
CSRF一般從第三方網站發起,被攻擊的網站沒法防止攻擊發生,只能經過加強本身網站針對CSRF的防禦能力來提高安全性。上文中講了CSRF的兩個特色:
針對這兩點,有以下經常使用的防禦策略,下面會有詳細說明:
這屬於在提交時要求附加本域才能獲取的信息的方式,這是源於CSRF大多來自第三方網站,經過直接禁止外域(或者不受信任的域名)對咱們發起請求的方式來防禦攻擊,那又是如何來實現這種方式呢?
在HTTP協議中,有一個Header叫Referer,用於標記來源域名。在瀏覽器發起請求時,大多數狀況會自動帶上這個Header,而且不能由前端自定義其內容,因此服務器能夠經過解析這個Header中的域名,肯定請求的來源域。對於Ajax請求,圖片和script等資源請求,Referer爲發起請求的頁面地址。對於頁面跳轉,Referer爲打開頁面歷史記錄的前一個頁面地址。所以經過Referer中連接的Origin部分就能夠得知請求的來源域名。
這種方法並不是萬無一失,Referer的值是由瀏覽器提供的,雖然HTTP協議上有明確的要求,可是每一個瀏覽器對於Referer的具體實現可能有差異,並不能保證瀏覽器自身沒有安全漏洞。使用驗證 Referer 值的方法,就是把安全性都依賴於第三方(即瀏覽器)來保障,從理論上來說,這樣並非很安全。在部分狀況下,攻擊者能夠隱藏,甚至修改本身請求的Referer。
前面說過,CSRF大多數狀況下來自第三方域名,但並不能排除本域發起。若是攻擊者有權限在本域發佈評論(含連接、圖片等,統稱UGC),那麼它能夠直接在本域發起攻擊,這種狀況下同源策略沒法達到防禦的做用。
同源驗證是一個相對簡單的防範方法,可以防範絕大多數的CSRF攻擊。但這並非萬無一失的,對於安全性要求較高,或者有較多用戶輸入內容的網站,咱們就要對關鍵的接口作額外的防禦措施。
CSRF的另外一個特徵是,攻擊者沒法直接竊取到用戶的信息(Cookie,Header,網站內容等),僅僅是冒用Cookie中的信息。而CSRF攻擊之因此可以成功,是由於服務器誤把攻擊者發送的請求當成了用戶本身的請求。那麼咱們能夠要求全部的用戶請求都攜帶一個CSRF攻擊者沒法獲取到的Token。服務器經過校驗請求是否攜帶正確的Token來把正常的請求和攻擊的請求區分開,這也能夠防範CSRF的攻擊。
基於CSRF Token的防禦策略大體分爲三個步驟:
首先,用戶打開頁面的時候,服務器須要給這個用戶生成一個Token,該Token經過加密算法對數據進行加密,通常Token都包括隨機字符串和時間戳的組合,顯然在提交時Token不能再放在Cookie中了,不然又會被攻擊者冒用。所以,爲了安全起見Token最好仍是存在服務器的Session中,以後在每次頁面加載時,使用JS遍歷整個DOM樹,對於DOM中全部的a和form標籤後加入Token。這樣能夠解決大部分的請求,可是對於在頁面加載以後動態生成的HTML代碼,這種方法就沒有做用,還須要程序員在編碼時手動添加Token。
對於GET請求,Token將附在請求地址以後,這樣URL就變成 http://url?csrftoken=tokenvalue。而對於 POST請求來講,要在form的最後加上:
<input type="hidden" name="csrftoken" value="tokenvalue"/>
當用戶從客戶端獲得了Token,再次提交給服務器的時候,服務器須要判斷Token的有效性,驗證過程是先解密Token,對比加密字符串以及時間戳,若是加密字符串一致且時間未過時,那麼這個Token就是有效的。
這種方法要比以前檢查Referer或者Origin要安全一些,Token能夠在產生並放於Session之中,而後在每次請求時把Token從Session中拿出,與請求中的Token進行比對,但這種方法的比較麻煩的在於如何把Token以參數的形式加入請求。 下面將以Java爲例,介紹一些CSRF Token的服務端校驗邏輯,代碼以下:
HttpServletRequest req = (HttpServletRequest)request; HttpSession s = req.getSession(); // 從sesion中獲得csrftoken屬性 String sToken = (String)s.getAttribute("csrftoken"); if(sToken == null){ // 產生新的token放入session中 sToken = generateToken(); s.setAttribute("csrftoken",sToken); chain.doFilter(request,response); }else{ // 從HTTP頭中取得csrftoken String xhrToken = req.getHeader("csrftoken"); // 從請求參數中取得csrftoken String pToken = req.getParameter("csrftoken"); if(sToken != null && xhrToken != null && sToken.equals(xhrToken)){ chain.doFilter(request,response); }else if(sToken != null & pToken != null && sToken.equals(pToken)){ chain.doFilter(request,response); }else{ request.getRequestDispatcher("error.jsp").forward(request,response); } }
Token是一個比較有效的CSRF防禦方法,只要頁面沒有XSS漏洞泄露Token,那麼接口的CSRF攻擊就沒法成功。可是此方法的實現比較複雜,須要給每個頁面都寫入Token(前端沒法使用純靜態頁面),每個Form及Ajax請求都攜帶這個Token,後端對每個接口都進行校驗,並保證頁面Token及請求Token一致。這就使得這個防禦策略不能在通用的攔截上統一攔截處理,而須要每個頁面和接口都添加對應的輸出和校驗。這種方法工做量巨大,且有可能遺漏。
在會話中存儲CSRF Token比較繁瑣,並且不能在通用的攔截上統一處理全部的接口。
那麼另外一種防護措施是使用雙重提交Cookie。利用CSRF攻擊不能獲取到用戶Cookie的特色,咱們能夠要求Ajax和表單請求攜帶一個Cookie中的值。
雙重Cookie採用如下流程:
此方法相對於CSRF Token就簡單了許多。能夠直接經過先後端攔截的的方法自動化實現。後端校驗也更加方便,只需進行請求中字段的對比,而不須要再進行查詢和存儲Token。
固然,此方法並無大規模應用,其在大型網站上的安全性仍是沒有CSRF Token高,緣由咱們舉例進行說明。
因爲任何跨域都會致使前端沒法獲取Cookie中的字段(包括子域名之間),因而發生了以下狀況:
使用雙重Cookie防護CSRF的優勢:
缺點:
本文簡單總結了一下CSRF的防禦策略:
除此以外,保證頁面的冪等性,後端接口不要在GET頁面中作用戶操做。爲了更好的防護CSRF,最佳實踐應該是結合上面總結的防護措施方式中的優缺點來綜合考慮,結合當前Web應用程序自身的狀況作合適的選擇,才能更好的預防CSRF的發生。
參考文獻: