(Cross Site Request Forgery, 跨站域請求僞造)是一種網絡的攻擊方式,它在 2007 年曾被列爲互聯網 20 大安全隱患之一,也被稱爲「One Click Attack」或者Session Riding,一般縮寫爲CSRF或者XSRF,是一種對網站的惡意利用。儘管聽起來像跨站腳本(XSS),但它與XSS很是不一樣,而且攻擊方式幾乎相左。XSS利用站點內的信任用戶,而CSRF則經過假裝來自受信任用戶
的請求來利用受信任的網站。與XSS攻擊相比,CSRF攻擊每每不大流行(所以對其進行防範的資源也至關稀少)和難以防範
,因此被認爲比XSS更具危險性
。javascript
你這能夠這麼理解CSRF攻擊:攻擊者盜用了你的身份,以你的名義發送惡意請求。CSRF可以作的事情包括:以你名義發送郵件,發消息,盜取你的帳號,甚至於購買商品,虛擬貨幣轉帳......形成的問題包括:我的隱私泄露以及財產安全。php
經過下圖來簡述原理html
從上圖能夠看出,要完成一次CSRF攻擊,受害者必須依次完成兩個步驟:java
1.你不能保證你登陸了一個網站後,再也不打開一個tab頁面並訪問另外的網站。
2.你不能保證你關閉瀏覽器了後,你本地的Cookie馬上過時,你上次的會話已經結束。(事實上,關閉瀏覽器不能結束一個會話,但大多數人都會錯誤的認爲關閉瀏覽器就等於退出登陸/結束會話了......)
3.上圖中所謂的攻擊網站,多是一個存在其餘漏洞的可信任的常常被人訪問的網站。程序員
詳細的原理介紹web
這種類型的CSRF通常是因爲程序員安全意識不強形成的。GET類型的CSRF利用很是簡單,只須要一個HTTP請求,因此,通常會這樣利用:算法
<img src=http://wooyun.org/csrf?xx=11 />
在訪問含有這個img的頁面後,成功向http://wooyun.org/csrf?xx=11
發出了一次HTTP請求。因此,若是將該網址替換爲存在GET型CSRF的地址,就能完成攻擊了。spring
烏雲案例瀏覽器
這種類型的CSRF危害沒有GET型的大,利用起來一般使用的是一個自動提交的表單,如:安全
<form action=http://wooyun.org/csrf.php method=POST> <input type="text" name="xx" value="11" /> </form> <script> document.forms[0].submit(); </script>
訪問該頁面後,表單會自動提交,至關於模擬用戶完成了一次POST操做。
過基礎認證的CSRF(經常使用於路由器):
POC:
<img src=http://admin:admin@192.168.1.1 />
加載該圖片後,路由器會給用戶一個合法的SESSION,就能夠進行下一步操做了。
烏雲案例
CRSF攻擊很大程度上是利用了瀏覽器的cookie,爲了防止站內的XSS漏洞盜取cookie,須要在cookie中設置"HttpOnly"屬性,這樣經過程序(js、applet等)沒法讀取到cookie信息。避免了攻擊者僞造cookie的狀況出現。
服務端的CSRF方式方法不少樣,但總的思想都是一致的,就是在客戶端頁面增長僞隨機數。
這多是最簡單的解決方案了,由於攻擊者不能得到第三方的Cookie(理論上),因此表單中的數據也就構造失敗了
示例:爲表單增長一個隱藏域,其值是cookie+「abc」的算法,後臺用相同的算法對比來判斷請求者是否合法。
csrf1.jsp
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>XSS</title> </head> <body> <form action="csrf1"> <input type="hidden" id="hash" name="hash" value="hash"> <table> <tr> <td>nick:</td> <td><input type="text" name="nick" value="xiaomao"></td> </tr> <tr> <td></td> <td><input type="submit" value="submit"></td> </tr> </table> </form> </body> </html> <script type="text/javascript"> //寫cookies function setCookie(name, value, exp) { document.cookie = name + "=" + escape(value) + ";expires=" + exp.toGMTString(); } //讀cookies function getCookie(name) { var arr, reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)"); if (arr = document.cookie.match(reg)) return unescape(arr[2]); else return null; } var exp = new Date(); exp.setTime(exp.getTime() + 3600); setCookie("uin", "duanxz", exp); var hash = getCookie("uin") + "abc"; document.getElementById("hash").value = hash; </script>
後臺的controller:
package com.dxz.web.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; @Controller public class Csrf1Controller { @RequestMapping(value="/csrf1",method=RequestMethod.GET) public ModelAndView helloWorld(@CookieValue(name="uin",defaultValue="0")String uin, @RequestParam("nick") String nick, @RequestParam("hash") String hash ){ String message = "uin cookie="+uin +", nick="+nick +", hash="+hash; System.out.println("/csrf1 receive:"+message); if((uin+"abc").equals(hash)) { System.out.println("he fa"); } else { System.out.println("fei fa"); } ModelAndView mv = new ModelAndView(); mv.addObject("message", message); mv.setViewName("csrf2"); return mv; } }
顯示結果:csrf2.jsp
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Annotation Spring MVC</title> </head> <body> <form action="login"> <table> <tr> <td>submit message:</td><td>${message}</td> </tr> </table> </form> </body> </html>
這個方案的思路是:每次的用戶提交都須要用戶在表單中填寫一個圖片上的隨機字符串,厄....這個方案能夠徹底解決CSRF,但我的以爲在易用性方面彷佛不是太好,還有聽聞是驗證碼圖片的使用涉及了一個被稱爲MHTML的Bug,可能在某些版本的微軟IE中受影響。
在業界目前防護 CSRF 攻擊主要有三種策略:驗證 HTTP Referer 字段;在請求地址中添加 token 並驗證;在 HTTP 頭中自定義屬性並驗證。下面就分別對這三種策略進行詳細介紹。
根據 HTTP 協議,在 HTTP 頭中有一個字段叫 Referer,它記錄了該 HTTP 請求的來源地址。在一般狀況下,訪問一個安全受限頁面的請求來自於同一個網站,好比須要訪問 http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory
,用戶必須先登錄 bank.example,而後經過點擊頁面上的按鈕來觸發轉帳事件。所以,要防護 CSRF 攻擊,網站只須要對於每個轉帳請求驗證其 Referer 值,若是是以 bank.example 開頭的域名,則說明該請求是來自銀行網站本身的請求,是合法的。若是 Referer 是其餘網站的話,則有多是黑客的 CSRF 攻擊,拒絕該請求。
這種方法的顯而易見的好處就是簡單易行,網站的普通開發人員不須要操心 CSRF 的漏洞,只須要在最後給全部安全敏感的請求統一增長一個攔截器來檢查 Referer 的值就能夠。
然而,這種方法並不是萬無一失。Referer 的值是由瀏覽器提供的,雖然 HTTP 協議上有明確的要求,可是每一個瀏覽器對於 Referer 的具體實現可能有差異,並不能保證瀏覽器自身沒有安全漏洞。使用驗證 Referer 值的方法,就是把安全性都依賴於第三方(即瀏覽器)來保障,從理論上來說,這樣並不安全。事實上,對於某些瀏覽器,好比IE6 或 FF2
,目前已經有一些方法能夠篡改 Referer 值。若是 網站支持IE6 瀏覽器
,黑客徹底能夠把用戶瀏覽器的 Referer 值設爲以 bank.example 域名開頭的地址,這樣就能夠經過驗證,從而進行 CSRF 攻擊。
即使是使用最新的瀏覽器,黑客沒法篡改 Referer 值,這種方法仍然有問題。由於 Referer 值會記錄下用戶的訪問來源,有些用戶認爲這樣會侵犯到他們本身的隱私權,特別是有些組織擔憂 Referer 值會把組織內網中的某些信息泄露到外網中。所以,用戶本身能夠設置瀏覽器使其在發送請求時再也不提供 Referer。當他們正常訪問銀行網站時,網站會由於請求沒有 Referer 值而認爲是 CSRF 攻擊,拒絕合法用戶的訪問。
CSRF 攻擊之因此可以成功,是由於黑客能夠徹底僞造用戶的請求,該請求中全部的用戶驗證信息都是存在於 cookie 中,所以黑客能夠在不知道這些驗證信息的狀況下直接利用用戶本身的 cookie 來經過安全驗證。要抵禦 CSRF,關鍵在於在請求中放入黑客所不能僞造的信息,而且該信息不存在於 cookie 之中。能夠在 HTTP 請求中以參數的形式加入一個隨機產生的 token,並在服務器端創建一個攔截器來驗證這個 token,若是請求中沒有 token 或者 token 內容不正確,則認爲多是 CSRF 攻擊而拒絕該請求。
這種方法要比檢查 Referer 要安全一些,token 能夠在用戶登錄後產生並放於 session 之中,而後在每次請求時把 token 從 session 中拿出,與請求中的 token 進行比對,但這種方法的難點在於如何把 token 以參數的形式加入請求。對於 GET 請求,token 將附在請求地址以後,這樣 URL 就變成 http://url?csrftoken=tokenvalue。 而對於 POST 請求來講,要在 form 的最後加上 <input type=」hidden」 name=」csrftoken」 value=」tokenvalue」/>,這樣就把 token 以參數的形式加入請求了。可是,在一個網站中,能夠接受請求的地方很是多,要對於每個請求都加上 token 是很麻煩的,而且很容易漏掉,一般使用的方法就是在每次頁面加載時,使用 javascript 遍歷整個 dom 樹,對於 dom 中全部的 a 和 form 標籤後加入 token。這樣能夠解決大部分的請求,可是對於在頁面加載以後動態生成的 html 代碼,這種方法就沒有做用,還須要程序員在編碼時手動添加 token。
該方法還有一個缺點是難以保證 token 自己的安全。特別是在一些論壇之類支持用戶本身發表內容的網站,黑客能夠在上面發佈本身我的網站的地址。因爲系統也會在這個地址後面加上 token,黑客能夠在本身的網站上獲得這個 token,並立刻就能夠發動 CSRF 攻擊。爲了不這一點,系統能夠在添加 token 的時候增長一個判斷,若是這個連接是鏈到本身本站的,就在後面添加 token,若是是通向外網則不加。不過,即便這個 csrftoken 不以參數的形式附加在請求之中,黑客的網站也一樣能夠經過 Referer 來獲得這個 token 值以發動 CSRF 攻擊。這也是一些用戶喜歡手動關閉瀏覽器 Referer 功能的緣由。
這種方法也是使用 token 並進行驗證,和上一種方法不一樣的是,這裏並非把 token 以參數的形式置於 HTTP 請求之中,而是把它放到 HTTP 頭中自定義的屬性裏。經過 XMLHttpRequest 這個類,能夠一次性給全部該類請求加上 csrftoken 這個 HTTP 頭屬性,並把 token 值放入其中。這樣解決了上種方法在請求中加入 token 的不便,同時,經過 XMLHttpRequest 請求的地址不會被記錄到瀏覽器的地址欄,也不用擔憂 token 會透過 Referer 泄露到其餘網站中去。
然而這種方法的侷限性很是大。XMLHttpRequest 請求一般用於 Ajax 方法中對於頁面局部的異步刷新,並不是全部的請求都適合用這個類來發起,並且經過該類請求獲得的頁面不能被瀏覽器所記錄下,從而進行前進,後退,刷新,收藏等操做,給用戶帶來不便。另外,對於沒有進行 CSRF 防禦的遺留系統來講,要採用這種方法來進行防禦,要把全部請求都改成 XMLHttpRequest 請求,這樣幾乎是要重寫整個網站,這代價無疑是不能接受的。
取得HTTP請求Referer:
String referer = request.getHeader("Referer");
下面將以 JavaScript 爲例,對上述三種方法分別用代碼進行示例。
1.驗證 Referer
// 從 HTTP 頭中取得 Referer 值 var referer=req.headers.Referer; // 判斷 Referer 是否以 test.example 開頭 if((referer!=null) && (_.startsWith(referer, 「test.example 」)){ // 驗證經過 }else{ // 驗證失敗,返回錯誤 }
2.驗證請求中的 token
var session = req.session; // 從 session 中獲得 csrftoken 屬性 var token = session.csrftoken; if(token == null){ // 產生新的 token 放入 session 中 token = generateToken(); token.csrftoken = token ; return .... } else{ // 從 HTTP 頭中取得 csrftoken var token = req.headers.csrftoken; // 從請求參數中取得 csrftoken var xhrToken = req.param('csrftoken'); if(token != null && xhrToken != null && token.equals(xhrToken)){ return ... }else{ return ... // ERROR } }
三、HTTP 頭中自定義屬性(網上例子)
var plainXhr = dojo.xhr; // 重寫 dojo.xhr 方法 dojo.xhr = function(method,args,hasBody) { // 確保 header 對象存在 args.headers = args.header || {}; tokenValue = '<%=request.getSession(false).getAttribute("csrftoken")%>'; var token = dojo.getObject("tokenValue"); // 把 csrftoken 屬性放到頭中 args.headers["csrftoken"] = (token) ? token : " "; return plainXhr(method,args,hasBody); };
CSRF 是一種危害很是大的攻擊,又很難以防範。目前幾種防護策略雖然能夠很大程度上抵禦 CSRF 的攻擊,但並無一種完美的解決方案。一些新的方案正在研究之中,好比對於每次請求都使用不一樣的動態口令,把 Referer 和 token 方案結合起來,甚至嘗試修改 HTTP 規範,可是這些新的方案尚不成熟,要正式投入使用並被業界廣爲接受還需時日。在這以前,咱們只有充分重視 CSRF,根據系統的實際狀況選擇最合適的策略,這樣才能把 CSRF 的危害降到最低
http://www.jianshu.com/p/7f33f9c7997b