跨站請求偽造(Cross-site request forgery),也被稱為 one-click attack 或者 session riding,通常縮寫為 CSRF 或者 XSRF, 是一種挾制用戶在當前已登錄的Web應用程序上執行非本意的操作的攻擊方法。跟跨網站腳本(XSS)相比,XSS 利用的是用戶對指定網站的信任,CSRF 利用的是網站對用戶網頁瀏覽器的信任。與XSS攻擊相比,CSRF攻擊往往不大流行(因此對其進行防范的資源也相當稀少)和難以防范,所以被認為比XSS更具危險性。
攻擊原理
跨站請求攻擊,簡單地說,是攻擊者通過一些技術手段欺騙用戶的瀏覽器去訪問一個自己曾經認證過的網站並運行一些操作(如發郵件,發消息,甚至財產操作如轉賬和購買商品)。由於瀏覽器曾經認證過,所以被訪問的網站會認為是真正的用戶操作而去運行。這利用了web中用戶身份驗證的一個漏洞:簡單的身份驗證只能保證請求發自某個用戶的瀏覽器,卻不能保證請求本身是用戶自願發出的。

從上圖可以看出,要完成一次CSRF攻擊,受害者必須依次完成下面步驟:
- 首先用戶C瀏覽並登錄了受信任站點A;
- 登錄信息驗證通過以后,站點A會在返回給瀏覽器的信息中帶上已登錄的cookie,cookie信息會在瀏覽器端保存一定時間(根據服務端設置而定);
- 完成這一步以后,用戶在沒有登出(清除站點A的cookie)站點A的情況下,訪問惡意站點B;
- 這時惡意站點 B的某個頁面向站點A發起請求,而這個請求會帶上瀏覽器端所保存的站點A的cookie;
- 站點A根據請求所帶的cookie,判斷此請求為用戶C所發送的。
透過例子能夠看出,攻擊者並不能通過CSRF攻擊來直接獲取用戶的賬戶控制權,也不能直接竊取用戶的任何信息。他們能做到的,是欺騙用戶瀏覽器,讓其以用戶的名義運行操作。
防御策略
驗證HTTP的Referer字段
HTTP頭中有一個Referer字段,這個字段用以標明請求來源於哪個地址。在處理敏感數據請求時,通常來說,Referer字段應和請求的地址位於同一域名下。
這種辦法簡單易行,工作量低,僅需要在最后給所有安全敏感的請求統一增加一個攔截器來檢查 Referer 的值就可以。
但這種辦法也有其局限性,因其完全依賴瀏覽器發送正確的Referer字段。雖然http協議對此字段的內容有明確的規定,但並無法保證來訪的瀏覽器的具體實現,亦無法保證瀏覽器沒有安全漏洞影響到此字段。並且也存在攻擊者攻擊某些瀏覽器,篡改其Referer字段的可能。
在請求地址中添加token並驗證
Token一般在生成頁面時,在Cookie中或者服務器的Session中保存一份,然后在頁面上用隱藏的input控件保存一份。當用戶發起請求時,帶上頁面上的Token做為參數,並在服務端將這個Token與Cookie或Session中的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。
在HTTP頭中自定義屬性並驗證
這種方法也是使用 token 並進行驗證,和上一種方法不同的是,這里並不是把 token 以參數的形式置於 HTTP 請求之中,而是把它放到 HTTP 頭中自定義的屬性里。通過 XMLHttpRequest 這個類,可以一次性給所有該類請求加上 csrftoken 這個 HTTP 頭屬性,並把 token 值放入其中。這樣解決了上種方法在請求中加入 token 的不便,同時,通過 XMLHttpRequest 請求的地址不會被記錄到瀏覽器的地址欄,也不用擔心 token 會透過 Referer 泄露到其他網站中去。
然而這種方法的局限性非常大。XMLHttpRequest 請求通常用於 Ajax 方法中對於頁面局部的異步刷新,並非所有的請求都適合用這個類來發起,而且通過該類請求得到的頁面不能被瀏覽器所記錄下,從而進行前進,后退,刷新,收藏等操作,給用戶帶來不便。另外,對於沒有進行 CSRF 防護的遺留系統來說,要采用這種方法來進行防護,要把所有請求都改為 XMLHttpRequest 請求,這樣幾乎是要重寫整個網站,這代價無疑是不能接受的。
將cookie設置為HttpOnly
CRSF攻擊很大程度上是利用了瀏覽器的cookie,為了防止站內的XSS漏洞盜取cookie,需要在cookie中設置“HttpOnly”屬性,這樣通過程序(如JavaScript腳本、Applet等)就無法讀取到cookie信息,避免了攻擊者偽造cookie的情況出現。
Java防范方法
在filter中對HTTP請求的Referer驗證
// 從 HTTP 頭中取得 Referer 值
String referer=request.getHeader("Referer");
// 判斷 Referer 是否以 bank.example(網站域名) 開頭
if((referer!=null) &&(referer.trim().startsWith(“bank.example”))){
chain.doFilter(request, response);
}else{
request.getRequestDispatcher(“error.jsp”).forward(request,response);
}
在 filter 中驗證請求中的 token
HttpServletRequest req = (HttpServletRequest)request;
HttpSession s = req.getSession();
// 從 session 中得到 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(xhrToken != null && sToken.equals(xhrToken)){
chain.doFilter(request, response);
}else if(pToken != null && sToken.equals(pToken)){
chain.doFilter(request, response);
}else{
request.getRequestDispatcher(“error.jsp”).forward(request,response);
}
}
首先判斷 session 中有沒有 csrftoken,如果沒有,則認為是第一次訪問,session 是新建立的,這時生成一個新的 token,放於 session 之中,並繼續執行請求。如果 session 中已經有 csrftoken,則說明用戶已經與服務器之間建立了一個活躍的 session,這時要看這個請求中有沒有同時附帶這個 token,由於請求可能來自於常規的訪問或是 XMLHttpRequest 異步訪問,我們分別嘗試從請求中獲取 csrftoken 參數以及從 HTTP 頭中獲取 csrftoken 自定義屬性並與 session 中的值進行比較,只要有一個地方帶有有效 token,就判定請求合法,可以繼續執行,否則就轉到錯誤頁面。生成 token 有很多種方法,任何的隨機算法都可以使用,Java 的 UUID 類也是一個不錯的選擇。
除了在服務器端利用 filter 來驗證 token 的值以外,我們還需要在客戶端給每個請求附加上這個 token,這是利用 js 來給 html 中的鏈接和表單請求地址附加 csrftoken 代碼,其中已定義 token 為全局變量,其值可以從 session 中得到。客戶端 html 中,主要是有兩個地方需要加上token,一個是表單form另一個就是標簽a。
將cookie設置為HttpOnly
在Java的Servlet的API中設置cookie為HttpOnly的代碼如下:
response.setHeader( "Set-Cookie", "cookiename=cookievalue;HttpOnly");
ASP.NET防范方法
CSRF能成功是因為同一個瀏覽器會共享Cookies,也就是說,通過權限認證和驗證是無法防止CSRF的。
我們需要在我們的頁面生成一個Token,發請求的時候把Token帶上。處理請求的時候需要驗證Cookies+Token。ASP.NET框架已經幫我們集成了處理方法:
-
在頁面上加入@Html.AntiForgeryToken()
<form action="/Test/UserAdd" id="addUser" method="post"> @Html.AntiForgeryToken() <input type="text" name="userName" value="normalUser"> </form> -
在后台代碼中加上 ValidateAntiForgeryToken
[HttpPost] [ValidateAntiForgeryToken] public ActionResult AddUser(string userName) { ViewBag.userName = userName; return View(); }
此時,如果此時發起CSRF攻擊,會提示錯誤。
