本文主要介紹和總結(jié)了CSRF跨站請求偽造的基本原理和主要防范措施,工作中有用到的朋友不妨收藏轉(zhuǎn)發(fā)一下,以備您參考。
什么是CSRF?
CSRF跨站點請求偽造(Cross—Site Request Forgery),跟類似XSS攻擊一樣,存在著巨大的安全隱患,你可以這樣來理解:攻擊者盜用了你的身份,以你的名義發(fā)送惡意請求,對服務器來說這個請求是完全合法的,但是卻偷偷完成了攻擊者所期望的一個操作,比如以你的名義發(fā)送郵件、發(fā)消息,盜取你的賬號,添加系統(tǒng)管理員,甚至于購買商品、轉(zhuǎn)賬等。
CSRF攻擊原理及過程
假如:Web A為存在CSRF漏洞的網(wǎng)站,Web B為攻擊者構(gòu)建的惡意網(wǎng)站,User C為Web A網(wǎng)站的合法用戶。攻擊大概過程如下:
- 用戶C打開瀏覽器,訪問受信任網(wǎng)站A,輸入用戶名和密碼請求登錄網(wǎng)站A。
- 在用戶信息通過驗證后,網(wǎng)站A產(chǎn)生Cookie信息并返回給瀏覽器,此時用戶登錄網(wǎng)站A成功,可以正常發(fā)送請求到網(wǎng)站A。
- 用戶未退出網(wǎng)站A之前,在同一瀏覽器中,打開一個TAB頁訪問網(wǎng)站B。
- 網(wǎng)站B接收到用戶請求后,返回一些攻擊性代碼,并發(fā)出一個請求要求訪問第三方站點A。
- 瀏覽器在接收到這些攻擊性代碼后,根據(jù)網(wǎng)站B的請求,在用戶不知情的情況下攜帶Cookie信息,向網(wǎng)站A發(fā)出請求。網(wǎng)站A并不知道該請求其實是由B發(fā)起的,所以會根據(jù)用戶C的Cookie信息以C的權(quán)限處理該請求,導致來自網(wǎng)站B的惡意代碼被執(zhí)行。
如何防御CSRF攻擊?
目前,我們工作中常用于防御CSRF攻擊主要有三種策略:
一、驗證 HTTP Referer字段
根據(jù) HTTP 協(xié)議,在 HTTP 頭 中有一個字段叫 Referer,它記錄了該 HTTP 請求的來源地址。通常情況下,訪問一個安全受限頁面的請求來自于同一個網(wǎng)站,比如需要訪問
http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory,用戶必須先登陸 bank.example,然后通過點擊頁面上的按鈕來觸發(fā)轉(zhuǎn)賬事件。這時,該轉(zhuǎn)賬請求的Referer值就會是轉(zhuǎn)賬按鈕所在的頁面的 URL,通常是以bank.example 域名開頭的地址。而如果黑客要對銀行網(wǎng)站實施 CSRF 攻擊,他只能在他自己的網(wǎng)站構(gòu)造請求,當用戶通過黑客的網(wǎng)站發(fā)送請求到銀行時,該請求的 Referer 是指向黑客自己的網(wǎng)站。因此,要防御 CSRF 攻擊,銀行網(wǎng)站只需要對于每一個轉(zhuǎn)賬請求驗證其 Referer 值,如果是以 bank.example 開頭的域名,則說明該請求是來自銀行網(wǎng)站自己的請求,是合法的。如果 Referer 是其他網(wǎng)站的話,則有可能是黑客的 CSRF 攻擊,拒絕該請求。
這種方法的顯而易見的好處就是簡單易行,網(wǎng)站的普通開發(fā)人員不需要操心 CSRF 的漏洞,只需要在最后給所有安全敏感的請求統(tǒng)一增加一個攔截器來檢查 Referer 的值就可以。特別是對于當前現(xiàn)有的系統(tǒng),不需要改變當前系統(tǒng)的任何已有代碼和邏輯,沒有風險,非常便捷。
然而,這種方法并非萬無一失。Referer 的值是由瀏覽器提供的,雖然 HTTP 協(xié)議上有明確的要求,但是每個瀏覽器對于 Referer 的具體實現(xiàn)可能有差別,并不能保證瀏覽器自身沒有安全漏洞。使用驗證 Referer 值的方法,就是把安全性都依賴于第三方(即瀏覽器)來保障,從理論上來講,這樣并不安全。事實上,對于某些瀏覽器,比如 IE6 或 FF2,目前已經(jīng)有一些方法可以篡改 Referer 值。如果 bank.example 網(wǎng)站支持IE6瀏覽器,黑客完全可以把用戶瀏覽器的 Referer 值設為以 bank.example 域名開頭的地址,這樣就可以通過驗證,從而進行 CSRF 攻擊。
即便是使用最新的瀏覽器,黑客無法篡改 Referer 值,這種方法仍然有問題。因為 Referer 值會記錄下用戶的訪問來源,有些用戶認為這樣會侵犯到他們自己的隱私權(quán),特別是有些組織擔心 Referer的值 會把組織內(nèi)網(wǎng)中的某些信息泄露到外網(wǎng)中。因此,用戶自己可以設置瀏覽器使其在發(fā)送請求時不再提供 Referer。當他們正常訪問銀行網(wǎng)站時,網(wǎng)站會因為請求沒有 Referer 值而認為是 CSRF 攻擊,拒絕合法用戶的訪問。
二、在請求地址中添加token并驗證
CSRF 攻擊之所以能夠成功,是因為黑客可以完全偽造用戶的請求,該請求中所有的用戶驗證信息都是存在于 cookie 中,因此黑客可以在不知道這些驗證信息的情況下直接利用用戶自己的 cookie 來通過安全驗證。要抵御 CSRF,關(guān)鍵在于在請求中放入黑客所不能偽造的信息,并且該信息不存在于 cookie 之中。可以在 HTTP 請求中以參數(shù)的形式加入一個隨機產(chǎn)生的 token,并在服務器端建立一個攔截器來驗證這個 token,如果請求中沒有 token 或者 token 內(nèi)容不正確,則認為可能是 CSRF 攻擊而拒絕該請求。
這種方法要比檢查 Referer 要安全一些,token可以在用戶登陸后產(chǎn)生并放于 session 之中,然后在每次請求時把token從 session 中拿出,與請求中的 token 進行比對,但這種方法的難點在于如何把token以參數(shù)的形式加入請求。對于 GET 請求,token 將附在請求地址之后,這樣 URL 就變成 http://url?csrftoken=tokenvalue。而對于 POST 請求來說,要在 form 的最后加上 <input type=”hidden” name=”csrftoken” value=”tokenvalue”/>,這樣就把 token 以參數(shù)的形式加入請求了。但是,在一個網(wǎng)站中,可以接受請求的地方非常多,要對于每一個請求都加上 token 是很麻煩的,并且很容易漏掉,通常使用的方法就是在每次頁面加載時,使用 JAVAscript 遍歷整個 dom 樹,對于 dom 中所有的 a 和 form 標簽后加入 token。這樣可以解決大部分的請求,但是對于在頁面加載之后動態(tài)生成的 html 代碼,這種方法就沒有作用,還需要程序員在編碼時手動添加 token。
該方法還有一個缺點是難以保證 token 本身的安全。特別是在一些論壇之類支持用戶自己發(fā)表內(nèi)容的網(wǎng)站,黑客可以在上面發(fā)布自己個人網(wǎng)站的地址。由于系統(tǒng)也會在這個地址后面加上 token,黑客可以在自己的網(wǎng)站上得到這個 token,并馬上就可以發(fā)動 CSRF 攻擊。為了避免這一點,系統(tǒng)可以在添加 token 的時候增加一個判斷,如果這個鏈接是鏈到自己本站的,就在后面添加 token,如果是通向外網(wǎng)則不加。不過,即使這個 csrftoken 不以參數(shù)的形式附加在請求之中,黑客的網(wǎng)站也同樣可以通過 Referer 來得到這個token值以發(fā)動 CSRF 攻擊。這也是一些用戶喜歡手動關(guān)閉瀏覽器 Referer 功能的原因。
三、在HTTP頭中自定義屬性并驗證
這種方法也是使用 token 并進行驗證,和上一種方法不同的是,這里并不是把 token 以參數(shù)的形式置于 HTTP 請求之中,而是把它放到 HTTP 頭中自定義的屬性里。通過 XMLHttpRequest 這個類,可以一次性給所有該類請求加上 csrftoken 這個 HTTP 頭屬性,并把 token 值放入其中。這樣解決了上種方法在請求中加入 token 的不便,同時,通過 XMLHttpRequest 請求的地址不會被記錄到瀏覽器的地址欄,也不用擔心 token 會透過 Referer 泄露到其他網(wǎng)站中去。
然而這種方法的局限性非常大。XMLHttpRequest 請求通常用于 Ajax方法中對于頁面局部的異步刷新,并非所有的請求都適合用這個類來發(fā)起,而且通過該類請求得到的頁面不能被瀏覽器所記錄下,從而進行前進,后退,刷新,收藏等操作,給用戶帶來不便。另外,對于沒有進行 CSRF防護的遺留系統(tǒng)來說,要采用這種方法來進行防護,要把所有請求都改為 XMLHttpRequest 請求,這樣幾乎是要重寫整個網(wǎng)站,這代價無疑是不能接受的。
SpringSecurity配置對csrf攻擊防護
從springsecurity3.2.0版本開始就已經(jīng)對csrf攻擊提供防護。
版本升級配置
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>3.2.0.RELEASE(及以上版本)</version>
</dependency>
HTTP配置
<!-- http安全配置 -->
<sec:http access-decision-manager-ref="accessDecisionManager" auto-config="true" entry-point-ref="authenticationEntryPoint">
<sec:logout />
<sec:anonymous granted-authority="A_ANONYMOUS,A_0" />
<sec:custom-filter ref="authenticationFilter" before="FORM_LOGIN_FILTER" />
<sec:custom-filter ref="filterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR" />
<sec:custom-filter ref="switchRoleProcessingFilter" before="SWITCH_USER_FILTER" />
<sec:custom-filter ref="tokenAuthenticationFilter" before="ANONYMOUS_FILTER" />
<sec:custom-filter ref="sessionManagementFilter" before="SESSION_MANAGEMENT_FILTER" />
<sec:custom-filter ref="loginOutFilter" before="LOGOUT_FILTER" />
<!--CSRF防護配置-->
<sec:csrf/>
</sec:http>
前端配置
一般我們的項目中都有一個公用文件(如公用js文件),每個頁面都會引用該文件,所以我們可以在通用文件中做類似如下配置:
<meta name="_csrf" content="${_csrf.token}"/>
<meta name="_csrf_header" content="${_csrf.headerName}"/>
<meta name="_csrf_parameter_name" content="${_csrf.parameterName}"/>
<script type="text/JavaScript">
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$.ajaxSetup({
beforeSend: function (xhr) {
if(header && token ){
xhr.setRequestHeader(header, token);
}
}}
);
</script>
$.ajaxSetup的意思就是給我們所有的ajax請求都加上這個header和token,或者放到form表單中:
<form action="${logoutUrl}" method="post">
<input type="submit" value="Log out" />
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
</form>
注意, _csrf這個要與spring security的配置文件中的配置相匹配,默認為_csrf。