轉載:https://blog.csdn.net/freeking101/article/details/86537087
From:https://www.daguanren.cc/post/csrf-introduction.html
From:https://blog.csdn.net/stpeace/article/details/53512283
CSRF 攻擊的應對之道:https://www.ibm.com/developerworks/cn/web/1102_niugang_csrf
WEB三大攻擊之—CSRF攻擊與防護:https://www.daguanren.cc/post/csrf-introduction.html
淺談 CSRF 攻擊方式:https://www.jianshu.com/p/ffb99fc70646
CSRF 攻擊與防禦:https://www.cnblogs.com/phpstudy2015-6/p/6771239.html
《白帽子講web安全》CSRF 例項:https://www.jianshu.com/p/94fd1a5d5413
CSRF例項:https://blog.csdn.net/wst0717/article/details/81542289
更多 CSRF 例項,可以自己搜尋
CSRF概念:
CSRF定義: 跨站請求偽造(英語:Cross-site request forgery)是一種對網站的惡意利用,也被稱為 one-click attack 或者 session riding,通常縮寫為 CSRF 或者 XSRF, 是一種挾制使用者在當前已登入的Web應用程式上執行非本意的操作的攻擊方法。 CSRF跨站點請求偽造(Cross—Site Request Forgery) 跟XSS攻擊一樣,存在巨大的危害性。
你可以這樣來理解:攻擊者盜用了你的身份,以你的名義傳送惡意請求,對伺服器來說這個請求是完全合法的,但是卻完成了攻擊者所期望的一個操作,比如以你的名義傳送郵件、發訊息,盜取你的賬號,新增系統管理員,甚至於購買商品、虛擬貨幣轉賬等。
簡單地說,是攻擊者透過一些技術手段欺騙使用者的瀏覽器去訪問一個自己曾經認證過的網站並執行一些操作(如發郵件,發訊息,甚至財產操作如轉賬和購買商品)。由於瀏覽器曾經認證過,所以被訪問的網站會認為是真正的使用者操作而去執行。這利用了web中使用者身份驗證的一個漏洞:簡單的身份驗證只能保證請求發自某個使用者的瀏覽器,卻不能保證請求本身是使用者自願發出的。
CSRF地位:是一種網路攻擊方式,是網際網路重大安全隱患之一,NYTimes.com(紐約時報)、Metafilter,YouTube、Gmail和百度HI都受到過此類攻擊。
對比XSS:跟跨網站指令碼(XSS)相比,XSS 利用的是使用者對指定網站的信任,CSRF 利用的是網站對使用者網頁瀏覽器的信任。
如下:其中Web A為存在CSRF漏洞的網站,Web B為攻擊者構建的惡意網站,User C為Web A網站的合法使用者。
如果上面 CSRF 原理看不懂,可以再看這個原理:
先了解第一方和第三方cookie概念
Cookie是一個域伺服器儲存在瀏覽器中的一小段資料塊,只能被這個域訪問,誰設定則誰訪問。
第一方Cookie:比如,訪問www.a.com這個網站,這個網站設定了一個Cookie,這個Cookie也只能被www.a.com這個域下的網頁讀取。
第三方Cookie:比如,訪問www.a.com這個網站,網頁裡有用到www.b.com網站的一張圖片,瀏覽器在www.b.com請求圖片的時候,www.b.com設定了一個Cookie,那這個Cookie只能被www.b.com這個域訪問,反而不能被www.a.com這個域訪問,因為對我們來說,我們實際是在訪問www.a.com這個網站被設定了一個www.b.com這個域下的Cookie,所以叫第三方Cookie。
CSRF 原理:
- 1.使用者C開啟瀏覽器,訪問受信任網站A,輸入使用者名稱和密碼請求登入網站A;
- 2.在使用者資訊透過驗證後,網站A產生Cookie資訊並返回給瀏覽器,此時使用者登入網站A成功,可以正常傳送請求到網站A;
- 3.使用者未退出網站A之前,在同一瀏覽器中,開啟一個TAB頁訪問網站B;
- 4.網站B接收到使用者請求後,返回一些攻擊性程式碼,併發出一個請求要求訪問第三方站點A;
- 5.瀏覽器在接收到這些攻擊性程式碼後,根據網站B的請求,在使用者不知情的情況下攜帶Cookie資訊,向網站A發出請求。網站A並不知道該請求其實是由B發起的,所以會根據使用者C的Cookie資訊以C的許可權處理該請求,導致來自網站B的惡意程式碼被執行。
簡而言之: 透過訪問惡意網址,惡意網址返回來js自動執行訪問你之前登陸的網址,因為你已經登入了,所以再次訪問將會攜帶cookie,因為伺服器只認有沒有cookie,無法區分是不是使用者正常的訪問,所以會欺騙伺服器,造成傷害
CSRF攻擊防禦
CSRF攻擊防禦的重點是利用cookie的值只能被第一方讀取,無法讀取第三方的cookie值。
防禦方法:
預防csrf攻擊簡單可行的方法就是在客戶端網頁上再次新增一個cookie,儲存一個隨機數,而使用者訪問的時候,先讀取這個cookie的值,hash一下這個cookie值併傳送給伺服器,伺服器接收到使用者的hash之後的值,同時取出之前設定在使用者端的cookie的值,用同樣的演算法hash這個cookie值,比較這兩個hash值,相同則是合法。(如果使用者訪問了病毒網站,也想帶這個cookie去訪問的時候,此時,因為病毒網站無法獲取第三方cookie的值,所以他也就無法hash這個隨機數,所以也就會被伺服器校驗的過濾掉)
session 工作原理
關於瀏覽器快取,cookie , session:http://www.cnblogs.com/yigeqi/p/6274602.html
理解Cookie和Session機制:https://www.cnblogs.com/andy-zhou/p/5360107.html
深入理解瀏覽器會話機制(session && cookie):https://blog.csdn.net/xi_2130/article/details/51361494
cookie 和session 的區別詳解:https://www.cnblogs.com/shiyangxt/articles/1305506.html
Cookie和Session詳解:https://blog.csdn.net/gaoyong_stone/article/details/79524321
CSRF 比 XSS更具危險性。想要深入理解 CSRF 的攻擊特性,我們有必要了解一下網站session的工作原理。
session 我想大家都不陌生,無論你用.net或PHP開發過網站的都肯定用過session物件,然而session它是如何工作的呢?如果你不清楚請往下看。
先問個小問題:如果我把瀏覽器的cookie禁用了,大家認為session還能正常工作嗎?
答案是否定的,我在這邊舉個簡單的例子幫助大家理解session。
比如我買了一張高爾夫俱樂部的會員卡,俱樂部給了我一張帶有卡號的會員卡。我能享受哪些權利(比如我是高階會員卡可以打19洞和後付費喝飲料,而初級會員卡只能在練習場揮杆)以及我的個人資料都是儲存在高爾夫俱樂部的資料庫裡的。我每次去高爾夫俱樂部只需要出示這張高階會員卡,俱樂部就知道我是誰了,並且為我服務了。
這裡我們的高階會員卡卡號 = 儲存在cookie的sessionid; 而我的高階會員卡權利和個人資訊就是服務端的session物件了。
我們知道http請求是無狀態的,也就是說每次http請求都是獨立的無關之前的操作的,但是每次http請求都會將本域下的所有cookie作為http請求頭的一部分傳送給服務端,所以服務端就根據請求中的cookie存放的sessionid去session物件中找到該會員資料了。
當然session的儲存方法多種多樣,可以儲存在檔案中,也可以記憶體裡,考慮到分散式的橫向擴充套件我們還是建議把它儲存在第三方媒介中,比如redis或者mongodb。
我們理解了session的工作機制後,CSRF也就很容易理解了。CSRF攻擊就相當於惡意使用者A複製了我的高階會員卡,哪天惡意使用者A也可以拿著這張假冒的高階會員卡去高爾夫俱樂部打19洞,享受美味的飲料了,而我在月底就會收到高爾夫俱樂部的賬單!
瞭解CSRF的機制之後,危害性我相信大家已經不言而喻了,我可以偽造某一個使用者的身份給其好友傳送垃圾資訊,這些垃圾資訊的超連結可能帶有木馬程式或者一些欺騙資訊(比如借錢之類的),如果CSRF傳送的垃圾資訊還帶有蠕蟲連結的話,那些接收到這些有害資訊的好友萬一開啟私信中的連線就也成為了有害資訊的散播著,這樣數以萬計的使用者被竊取了資料種植了木馬。整個網站的應用就可能在瞬間奔潰,使用者投訴,使用者流失,公司聲譽一落千丈甚至面臨倒閉。曾經在MSN上,一個美國的19歲的小夥子Samy利用css的background漏洞幾小時內讓100多萬使用者成功的感染了他的蠕蟲,雖然這個蠕蟲並沒有破壞整個應用,只是在每一個使用者的簽名後面都增加了一句“Samy 是我的偶像”,但是一旦這些漏洞被惡意使用者利用,後果將不堪設想,同樣的事情也曾經發生在新浪微博上面。
舉例:
CSRF攻擊的主要目的是讓使用者在不知情的情況下攻擊自己已登入的一個系統,類似於釣魚。如使用者當前已經登入了郵箱,或bbs,同時使用者又在使用另外一個,已經被你控制的站點,我們姑且叫它釣魚網站。這個網站上面可能因為某個圖片吸引你,你去點選一下,此時可能就會觸發一個js的點選事件,構造一個bbs發帖的請求,去往你的bbs發帖,由於當前你的瀏覽器狀態已經是登陸狀態,所以session登陸cookie資訊都會跟正常的請求一樣,純天然的利用當前的登陸狀態,讓使用者在不知情的情況下,幫你發帖或幹其他事情。
CSRF攻擊攻擊原理及過程如下:
1. 使用者C開啟瀏覽器,訪問受信任網站A,輸入使用者名稱和密碼請求登入網站A;
2. 在使用者資訊透過驗證後,網站A產生Cookie資訊並返回給瀏覽器,此時使用者登入網站A成功,可以正常傳送請求到網站A;
3. 使用者未退出網站A之前,在同一瀏覽器中,開啟一個TAB頁訪問網站B;
4. 網站B接收到使用者請求後,返回一些攻擊性程式碼,併發出一個請求要求訪問第三方站點A;
5. 瀏覽器在接收到這些攻擊性程式碼後,根據網站B的請求,在使用者不知情的情況下攜帶Cookie資訊,向網站A發出請求。網站A並不知道該請求其實是由B發起的,所以會根據使用者C的Cookie資訊以C的許可權處理該請求,導致來自網站B的惡意程式碼被執行。
所以要被CSRF攻擊,必須同時滿足兩個條件:
- 登入受信任網站A,並在本地生成Cookie。
- 在不登出A的情況下,訪問危險網站B。
幾種常見的攻擊型別
烏雲案例:GET型別的 CSRF
這種型別的CSRF一般是由於程式設計師安全意識不強造成的。GET型別的CSRF利用非常簡單,只需要一個HTTP請求,所以,一般會這樣利用:
<img src=http://wooyun.org/csrf?xx=11 />
在訪問含有這個img的頁面後,成功向http://wooyun.org/csrf?xx=11 發出了一次HTTP請求。所以,如果將該網址替換為存在GET型CSRF的地址,就能完成攻擊了。
烏雲案例:POST型別的 CSRF
這種型別的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
過基礎認證的CSRF(常用於路由器):
POC:
<img src=http://admin:admin@192.168.1.1 />
載入該圖片後,路由器會給使用者一個合法的 SESSION,就可以進行下一步操作了。
CSRF 攻擊例項:
受害者 Bob 在銀行有一筆存款,透過對銀行的網站傳送請求 http://bank.example/withdraw?account=bob&amount=1000000&for=bob2 可以使 Bob 把 1000000 的存款轉到 bob2 的賬號下。通常情況下,該請求傳送到網站後,伺服器會先驗證該請求是否來自一個合法的 session,並且該 session 的使用者 Bob 已經成功登陸。
駭客 Mallory 自己在該銀行也有賬戶,他知道上文中的 URL 可以把錢進行轉帳操作。Mallory 可以自己傳送一個請求給銀行:http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory。但是這個請求來自 Mallory 而非 Bob,他不能透過安全認證,因此該請求不會起作用。
這時,Mallory 想到使用 CSRF 的攻擊方式,他先自己做一個網站,在網站中放入如下程式碼: src=”http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory ”,並且透過廣告等誘使 Bob 來訪問他的網站。當 Bob 訪問該網站時,上述 url 就會從 Bob 的瀏覽器發向銀行,而這個請求會附帶 Bob 瀏覽器中的 cookie 一起發向銀行伺服器。大多數情況下,該請求會失敗,因為他要求 Bob 的認證資訊。但是,如果 Bob 當時恰巧剛訪問他的銀行後不久,他的瀏覽器與銀行網站之間的 session 尚未過期,瀏覽器的 cookie 之中含有 Bob 的認證資訊。這時,悲劇發生了,這個 url 請求就會得到響應,錢將從 Bob 的賬號轉移到 Mallory 的賬號,而 Bob 當時毫不知情。等以後 Bob 發現賬戶錢少了,即使他去銀行查詢日誌,他也只能發現確實有一個來自於他本人的合法請求轉移了資金,沒有任何被攻擊的痕跡。而 Mallory 則可以拿到錢後逍遙法外。
CSRF 攻擊例項
daguanren(大官人)在銀行有一筆存款,輸入使用者名稱密碼登入銀行網銀後傳送請求進行個人名下賬戶轉賬 :
http://www.bank.example/withdraw?account=daguanren1&amount=999&for=daguanren2
將 daguanren1 中的 999 塊轉到了 daguanren2 賬號中。通常使用者登入後,系統會儲存使用者登入的session值(可能是使用者手機號、賬號等)。但如果這時daguanren不小心新開一個tab頁面進入了一個駭客jinlian(金蓮)的網站,而金蓮網站的頁面中嵌有如下html標籤:
-
-
<html>
-
<!--其他頁面元素-->
-
-
<img src=http://www.bank.example/withdraw?account=daguanren1&amount=888&for=jinlian width='0' height='0'>
-
-
<!--其他頁面元素-->
-
</html>
這個請求就會附帶上daguanren的session值,成功將大官人的888元轉至jinlian的賬戶上。但如果daguanren之前沒有登入網銀,而是直接開啟jinlian的網站,則由於沒有session值,不會被攻擊。以上示例雖然是get請求,post請求提交的表單同樣會被攻擊。
-
<iframe style="display:none" name="csrf-frame"></iframe>
-
<form method='POST' action='http://www.bank.example/withdraw' target="csrf-frame" id="csrf-form">
-
<input type='hidden' name='account' value='daguanren1'>
-
<input type='hidden' name='amount' value='888'>
-
<input type='hidden' name='for' value='jinlian'>
-
<input type='submit' value='submit'>
-
</form>
-
<script>document.getElementById("csrf-form").submit()</script>
CSRF 攻擊的物件
在討論如何抵禦 CSRF 之前,先要明確 CSRF 攻擊的物件,也就是要保護的物件。從以上的例子可知,CSRF 攻擊是駭客藉助受害者的 cookie(session) 騙取伺服器的信任,但是駭客並不能拿到 cookie,也看不到 cookie 的內容。另外,對於伺服器返回的結果,由於瀏覽器同源策略的限制,駭客也無法進行解析。因此,駭客無法從返回的結果中得到任何東西,他所能做的就是給伺服器傳送請求,以執行請求中所描述的命令,在伺服器端直接改變資料的值,而非竊取伺服器中的資料。所以,我們要保護的物件是那些可以直接產生資料改變的服務,而對於讀取資料的服務,則不需要進行 CSRF 的保護。比如銀行系統中轉賬的請求會直接改變賬戶的金額,會遭到 CSRF 攻擊,需要保護。而查詢餘額是對金額的讀取操作,不會改變資料,CSRF 攻擊無法解析伺服器返回的結果,無需保護。
故:增刪改需要防範CSRF攻擊,而讀(即讀資料庫)無需防範。
舉個例子
簡單版:
假如部落格園有個加關注的GET介面,blogUserGuid引數很明顯是關注人Id,如下:
http://www.cnblogs.com/mvc/Follow/FollowBlogger.aspx?blogUserGuid=4e8c33d0-77fe-df11-ac81-842b2b196315
那我只需要在我的一篇博文內容裡面寫一個img標籤:
<img style="width:0;" src="http://www.cnblogs.com/mvc/Follow/FollowBlogger.aspx?blogUserGuid=4e8c33d0-77fe-df11-ac81-842b2b196315" />
那麼只要有人開啟我這篇博文,那就會自動關注我。
升級版:
假如部落格園還是有個加關注的介面,不過已經限制了只獲取POST請求的資料。這個時候就做一個第三方的頁面,但裡面包含form提交程式碼,然後透過QQ、郵箱等社交工具傳播,誘惑使用者去開啟,那開啟過部落格園的使用者就中招了。
在說例子之前要糾正一個iframe問題,有人會直接在第三方頁面這樣寫。如下:
-
-
<html lang="en-US">
-
<head>
-
<title>CSRF SHOW</title>
-
</head>
-
<body>
-
<!--不嵌iframe會跳轉-->
-
<iframe style="display:none;">
-
<form name="form1" action="http://www.cnblogs.com/mvc/Follow/FollowBlogger.aspx" method="post">
-
<input type="hidden" name="blogUserGuid" value="4e8c33d0-77fe-df11-ac81-842b2b196315"/>
-
<input type="submit" value>
-
</form>
-
<script>
-
document.forms.form1.submit();
-
</script>
-
</iframe>
-
</body>
-
</html>
這樣是用問題的,由於同源策略的原因,iframe內容根本載入不出來,所以裡面form提交當然不會執行。
PS:我嘗試了chrome、IE11、Firefox,情況都是這樣。
所以可以用嵌多一層頁面方式解決,如下:
第一個展示頁面(test):
-
-
<html lang="en-US">
-
<head>
-
<title>CSRF SHOW</title>
-
</head>
-
<body>
-
<iframe style="display:none;" src="test2.html"></iframe>
-
</body>
-
</html>
第二個隱藏頁面(test2):
-
-
<html lang="en-US">
-
<head>
-
<title>CSRF GET</title>
-
<body>
-
<form name="form1" action="http://www.cnblogs.com/mvc/Follow/FollowBlogger.aspx" method="post">
-
<input type="hidden" name="blogUserGuid" value="4e8c33d0-77fe-df11-ac81-842b2b196315"/>
-
<input type="submit" value>
-
</form>
-
<script>
-
document.forms.form1.submit();
-
</script>
-
</body>
-
</html>
這樣就可以解決了,有人會問為什麼要加多一層iframe,因為不嵌iframe頁面會重定向,這樣就降低了攻擊的隱蔽性。另外我們test頁面不使用XMLHTTPRequest傳送POST請求,是因為有跨域的問題,而form可以跨域post資料。
進階版:
假如部落格園還是有個加關注的介面,已經限制POST,但博文內容是直接貼進HTML(未過濾),那就遭受XSS攻擊。那麼就可以直接把上面程式碼嵌入博文,那麼只要有人開啟我這篇博文,還是會自動關注我,這組合攻擊方式稱為XSRF。
CSRF攻擊的本質原因
CSRF攻擊是源於Web的隱式身份驗證機制!Web的身份驗證機制雖然可以保證一個請求是來自於某個使用者的瀏覽器,但卻無法保證該請求是使用者批准傳送的。CSRF攻擊的一般是由服務端解決。
上面大概地講了一下CSRF攻擊的思想,下面我將用幾個例子詳細說說具體的CSRF攻擊,這裡我以一個銀行轉賬的操作作為例子(僅僅是例子,真實的銀行網站沒這麼傻:>)
示例1:
銀行網站A,它以GET請求來完成銀行轉賬的操作,如:http://www.mybank.com/Transfer.php?toBankId=11&money=1000
危險網站B,它裡面有一段HTML的程式碼如下:
<img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>
首先,你登入了銀行網站A,然後訪問危險網站B,噢,這時你會發現你的銀行賬戶少了1000塊......
為什麼會這樣呢?原因是銀行網站A違反了HTTP規範,使用GET請求更新資源。在訪問危險網站B的之前,你已經登入了銀行網站A,而B中 的<img>以GET的方式請求第三方資源(這裡的第三方就是指銀行網站了,原本這是一個合法的請求,但這裡被不法分子利用了),所以你的瀏 覽器會帶上你的銀行網站A的Cookie發出Get請求,去獲取資源“http://www.mybank.com /Transfer.php?toBankId=11&money=1000”,結果銀行網站伺服器收到請求後,認為這是一個更新資源操作(轉賬 操作),所以就立刻進行轉賬操作......
示例2:
為了杜絕上面的問題,銀行決定改用POST請求完成轉賬操作。
銀行網站A的WEB表單如下:
<form action="Transfer.php" method="POST">
<p>ToBankId: <input type="text" name="toBankId" /></p>
<p>Money: <input type="text" name="money" /></p>
<p><input type="submit" value="Transfer" /></p>
</form>
後臺處理頁面Transfer.php如下:
<?php
session_start();
if (isset($_REQUEST['toBankId'] && isset($_REQUEST['money']))
{
buy_stocks($_REQUEST['toBankId'], $_REQUEST['money']);
}
?>
危險網站B,仍然只是包含那句HTML程式碼:
<img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>
和示例1中的操作一樣,你首先登入了銀行網站A,然後訪問危險網站B,結果.....和示例1一樣,你再次沒了1000塊~T_T,這次事故的 原因是:銀行後臺使用了$_REQUEST去獲取請求的資料,而$_REQUEST既可以獲取GET請求的資料,也可以獲取POST請求的資料,這就造成 了在後臺處理程式無法區分這到底是GET請求的資料還是POST請求的資料。在PHP中,可以使用$_GET和$_POST分別獲取GET請求和POST 請求的資料。在JAVA中,用於獲取請求資料request一樣存在不能區分GET請求資料和POST資料的問題。
示例3:
經過前面2個慘痛的教訓,銀行決定把獲取請求資料的方法也改了,改用$_POST,只獲取POST請求的資料,後臺處理頁面Transfer.php程式碼如下:
<?php
session_start();
if (isset($_POST['toBankId'] && isset($_POST['money']))
{
buy_stocks($_POST['toBankId'], $_POST['money']);
}
?>
然而,危險網站B與時俱進,它改了一下程式碼:
<html>
<head>
<script type="text/javascript">
function steal()
{
iframe = document.frames["steal"];
iframe.document.Submit("transfer");
}
</script>
</head>
<body οnlοad="steal()">
<iframe name="steal" display="none">
<form method="POST" name="transfer" action="http://www.myBank.com/Transfer.php">
<input type="hidden" name="toBankId" value="11">
<input type="hidden" name="money" value="1000">
</form>
</iframe>
</body>
</html>
如果使用者仍是繼續上面的操作,很不幸,結果將會是再次不見1000塊......因為這裡危險網站B暗地裡傳送了POST請求到銀行!
總結一下上面3個例子,CSRF主要的攻擊模式基本上是以上的3種,其中以第1,2種最為嚴重,因為觸發條件很簡單,一 個<img>就可以了,而第3種比較麻煩,需要使用JavaScript,所以使用的機會會比前面的少很多,但無論是哪種情況,只要觸發了 CSRF攻擊,後果都有可能很嚴重。
理解上面的3種攻擊模式,其實可以看出,CSRF攻擊是源於WEB的隱式身份驗證機制!WEB的身份驗證機制雖然可以保證一個請求是來自於某個使用者的瀏覽器,但卻無法保證該請求是使用者批准傳送的!
CSRF漏洞檢測:
檢測CSRF漏洞是一項比較繁瑣的工作,最簡單的方法就是抓取一個正常請求的資料包,去掉Referer欄位後再重新提交,如果該提交還有效,那麼基本上可以確定存在CSRF漏洞。
隨著對CSRF漏洞研究的不斷深入,不斷湧現出一些專門針對CSRF漏洞進行檢測的工具,如CSRFTester,CSRF Request Builder等。
以CSRFTester工具為例,CSRF漏洞檢測工具的測試原理如下:使用CSRFTester進行測試時,首先需要抓取我們在瀏覽器中訪問過的所有連結以及所有的表單等資訊,然後透過在CSRFTester中修改相應的表單等資訊,重新提交,這相當於一次偽造客戶端請求。如果修改後的測試請求成功被網站伺服器接受,則說明存在CSRF漏洞,當然此款工具也可以被用來進行CSRF攻擊。
當前防禦 CSRF 的幾種策略:
在業界目前防禦 CSRF 攻擊主要有四種策略:
- 驗證 HTTP Referer 欄位;
- 在請求地址中新增 token 並驗證;
- 在 HTTP 頭中自定義屬性並驗證;
- Chrome 瀏覽器端啟用 SameSite cookie
(1)驗證 HTTP Referer 欄位
什麼是HTTP Referer?下面GIF圖是由百度跳轉到QQ郵箱頁面的Referer檢視示意:
可以看出Referer為
Referer:https://www.baidu.com/
根據 HTTP 協議,在 HTTP 頭中有一個欄位叫 Referer,它記錄了該 HTTP 請求的來源地址。在通常情況下,訪問一個安全受限頁面的請求來自於同一個網站,比如需要訪問 http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory,使用者必須先登陸 bank.example,然後透過點選頁面上的按鈕來觸發轉賬事件。這時,該轉帳請求的 Referer 值就會是轉賬按鈕所在的頁面的 URL,通常是以 bank.example 域名開頭的地址。而如果駭客要對銀行網站實施 CSRF 攻擊,他只能在他自己的網站構造請求,當使用者透過駭客的網站傳送請求到銀行時,該請求的 Referer 是指向駭客自己的網站。因此,要防禦 CSRF 攻擊,銀行網站只需要對於每一個轉賬請求驗證其 Referer 值,如果是以 bank.example 開頭的域名,則說明該請求是來自銀行網站自己的請求,是合法的。如果 Referer 是其他網站的話,則有可能是駭客的 CSRF 攻擊,拒絕該請求。
這種方法的顯而易見的好處就是簡單易行,網站的普通開發人員不需要操心 CSRF 的漏洞,只需要在最後給所有安全敏感的請求統一增加一個攔截器來檢查 Referer 的值就可以。特別是對於當前現有的系統,不需要改變當前系統的任何已有程式碼和邏輯,沒有風險,非常便捷。
然而,這種方法並非萬無一失。Referer 的值是由瀏覽器提供的,雖然 HTTP 協議上有明確的要求,但是每個瀏覽器對於 Referer 的具體實現可能有差別,並不能保證瀏覽器自身沒有安全漏洞。使用驗證 Referer 值的方法,就是把安全性都依賴於第三方(即瀏覽器)來保障,從理論上來講,這樣並不安全。事實上,對於某些瀏覽器,比如 IE6 或 FF2,目前已經有一些方法可以篡改 Referer 值。如果 bank.example 網站支援 IE6 瀏覽器,駭客完全可以把使用者瀏覽器的 Referer 值設為以 bank.example 域名開頭的地址,這樣就可以透過驗證,從而進行 CSRF 攻擊。
即便是使用最新的瀏覽器,駭客無法篡改 Referer 值,這種方法仍然有問題。因為 Referer 值會記錄下使用者的訪問來源,有些使用者認為這樣會侵犯到他們自己的隱私權,特別是有些組織擔心 Referer 值會把組織內網中的某些資訊洩露到外網中。因此,使用者自己可以設定瀏覽器使其在傳送請求時不再提供 Referer。當他們正常訪問銀行網站時,網站會因為請求沒有 Referer 值而認為是 CSRF 攻擊,拒絕合法使用者的訪問。
另外,如果Referer的判斷邏輯寫的不嚴密的話,也容易被攻破,例如
-
const referer = request.headers.referer;
-
if (referer.indexOf('www.bank.example') > -1) {
-
// pass
-
}
如果駭客的網站是www.bank.example.hack.com,則referer檢查無效。
(2)在請求地址中新增 token 並驗證
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,駭客可以在自己的網站上得到這個 token,並馬上就可以發動 CSRF 攻擊。為了避免這一點,系統可以在新增 token 的時候增加一個判斷,如果這個連結是鏈到自己本站的,就在後面新增 token,如果是通向外網則不加。不過,即使這個 csrftoken 不以引數的形式附加在請求之中,駭客的網站也同樣可以透過 Referer 來得到這個 token 值以發動 CSRF 攻擊。這也是一些使用者喜歡手動關閉瀏覽器 Referer 功能的原因。
(3)在 HTTP 頭中自定義屬性並驗證
這種方法也是使用 token 並進行驗證,和上一種方法不同的是,這裡並不是把 token 以引數的形式置於 HTTP 請求之中,而是把它放到 HTTP 頭中自定義的屬性裡。透過 XMLHttpRequest 這個類,可以一次性給所有該類請求加上 csrftoken 這個 HTTP 頭屬性,並把 token 值放入其中。這樣解決了上種方法在請求中加入 token 的不便,同時,透過 XMLHttpRequest 請求的地址不會被記錄到瀏覽器的位址列,也不用擔心 token 會透過 Referer 洩露到其他網站中去。
然而這種方法的侷限性非常大。XMLHttpRequest 請求通常用於 Ajax 方法中對於頁面區域性的非同步重新整理,並非所有的請求都適合用這個類來發起,而且透過該類請求得到的頁面不能被瀏覽器所記錄下,從而進行前進,後退,重新整理,收藏等操作,給使用者帶來不便。另外,對於沒有進行 CSRF 防護的遺留系統來說,要採用這種方法來進行防護,要把所有請求都改為 XMLHttpRequest 請求,這樣幾乎是要重寫整個網站,這代價無疑是不能接受的。
4、Chrome瀏覽器端啟用SameSite cookie
下面介紹如何啟用SameSite cookie的設定,很簡單。
原本的 Cookie 的 header 設定是長這樣:
Set-Cookie: session_id=esadfas325
需要在尾部增加 SameSite 就好:
Set-Cookie: session_id=esdfas32e5; SameSite
SameSite 有兩種模式,Lax跟Strict模式,預設啟用Strict模式,可以自己指定模式:
-
Set-Cookie: session_id=esdfas32e5; SameSite=Strict
-
Set-Cookie: foo=bar; SameSite=Lax
Strict模式規定 cookie 只允許相同的site使用,不應該在任何的 cross site request 被加上去。即a標籤、form表單和XMLHttpRequest提交的內容,只要是提交到不同的site去,就不會帶上cookie。
但也存在不便,例如朋友傳送過來我已經登陸過的一個頁面連結,我點開後,該頁面仍然需要重新登入。
有兩種處理辦法,第一種是與Amazon一樣,準備兩組不同的cookie,第一組用於維持登入狀態不設定SameSite,第二組針對的是一些敏感操作會用到(例如購買、支付、設定賬戶等)嚴格設定SameSite。
基於這個思路,就產生了 SameSite 的另一一種模式:Lax模式。
Lax 模式開啟了一些限制,例如
-
<a>
-
<link rel="prerender">
-
<form method="GET">
這些都會帶上cookie。但是 POST 方法 的 form,或是隻要是 POST, PUT, DELETE 這些方法,就不會帶cookie。
但一定注意將重要的請求方式改成POST,否則GET仍然會被攻擊。
PS:該方式目前僅Chrome支援。
CSRF工具的防禦手段
1. 儘量使用POST,限制GET
GET介面太容易被拿來做CSRF攻擊,看第一個示例就知道,只要構造一個img標籤,而img標籤又是不能過濾的資料。介面最好限制為POST使用,GET則無效,降低攻擊風險。
當然POST並不是萬無一失,攻擊者只要構造一個form就可以,但需要在第三方頁面做,這樣就增加暴露的可能性。
2. 瀏覽器Cookie策略
IE6、7、8、Safari會預設攔截第三方本地Cookie(Third-party Cookie)的傳送。但是Firefox2、3、Opera、Chrome、Android等不會攔截,所以透過瀏覽器Cookie策略來防禦CSRF攻擊不靠譜,只能說是降低了風險。
PS:Cookie分為兩種,Session Cookie(在瀏覽器關閉後,就會失效,儲存到記憶體裡),Third-party Cookie(即只有到了Exprie時間後才會失效的Cookie,這種Cookie會儲存到本地)。
PS:另外如果網站返回HTTP頭包含P3P Header,那麼將允許瀏覽器傳送第三方Cookie。
3. 加驗證碼
驗證碼,強制使用者必須與應用進行互動,才能完成最終請求。在通常情況下,驗證碼能很好遏制CSRF攻擊。但是出於使用者體驗考慮,網站不能給所有的操作都加上驗證碼。因此驗證碼只能作為一種輔助手段,不能作為主要解決方案。
4. Referer Check
Referer Check在Web最常見的應用就是“防止圖片盜鏈”。同理,Referer Check也可以被用於檢查請求是否來自合法的“源”(Referer值是否是指定頁面,或者網站的域),如果都不是,那麼就極可能是CSRF攻擊。
但是因為伺服器並不是什麼時候都能取到Referer,所以也無法作為CSRF防禦的主要手段。但是用Referer Check來監控CSRF攻擊的發生,倒是一種可行的方法。
5. Anti CSRF Token
現在業界對CSRF的防禦,一致的做法是使用一個Token(Anti CSRF Token)。
例子:
1. 使用者訪問某個表單頁面。
2. 服務端生成一個Token,放在使用者的Session中,或者瀏覽器的Cookie中。
3. 在頁面表單附帶上Token引數。
4. 使用者提交請求後, 服務端驗證表單中的Token是否與使用者Session(或Cookies)中的Token一致,一致為合法請求,不是則非法請求。
這個Token的值必須是隨機的,不可預測的。由於Token的存在,攻擊者無法再構造一個帶有合法Token的請求實施CSRF攻擊。另外使用Token時應注意Token的保密性,儘量把敏感操作由GET改為POST,以form或AJAX形式提交,避免Token洩露。
注意:
CSRF的Token僅僅用於對抗CSRF攻擊。當網站同時存在XSS漏洞時候,那這個方案也是空談。所以XSS帶來的問題,應該使用XSS的防禦方案予以解決。
總結
CSRF攻擊是攻擊者利用使用者的身份操作使用者帳戶的一種攻擊方式,通常使用Anti CSRF Token來防禦CSRF攻擊,同時要注意Token的保密性和隨機性。
程式碼示例
以下JAVA程式碼示例,主要摘自這個文章CSRF 攻擊的應對之道
下文對上述前三種方法分別用程式碼進行示例。無論使用何種方法,在伺服器端的攔截器必不可少,它將負責檢查到來的請求是否符合要求,然後視結果而決定是否繼續請求或者丟棄。在 Java 中,攔截器是由 Filter 來實現的。我們可以編寫一個 Filter,並在 web.xml 中對其進行配置,使其對於訪問所有需要 CSRF 保護的資源的請求進行攔截。 在 filter 中對請求的 Referer 驗證程式碼如下
清單 1. 在 Filter 中驗證 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);
-
}
以上程式碼先取得 Referer 值,然後進行判斷,當其非空並以 bank.example 開頭時,則繼續請求,否則的話可能是 CSRF 攻擊,轉到 error.jsp 頁面。
如果要進一步驗證請求中的 token 值,程式碼如下
清單 2. 在 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(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);
-
}
-
}
首先判斷 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 中得到。
清單 3. 在客戶端對於請求附加 token
-
-
function appendToken(){
-
updateForms();
-
updateTags();
-
}
-
-
function updateForms() {
-
// 得到頁面中所有的 form 元素
-
var forms = document.getElementsByTagName('form');
-
for(i=0; i<forms.length; i++) {
-
var url = forms[i].action;
-
-
// 如果這個 form 的 action 值為空,則不附加 csrftoken
-
if(url == null || url == "" ) continue;
-
-
// 動態生成 input 元素,加入到 form 之後
-
var e = document.createElement("input");
-
e.name = "csrftoken";
-
e.value = token;
-
e.type="hidden";
-
forms[i].appendChild(e);
-
}
-
}
-
-
function updateTags() {
-
var all = document.getElementsByTagName('a');
-
var len = all.length;
-
-
// 遍歷所有 a 元素
-
for(var i=0; i<len; i++) {
-
var e = all[i];
-
updateTag(e, 'href', token);
-
}
-
}
-
-
function updateTag(element, attr, token) {
-
var location = element.getAttribute(attr);
-
if(location != null && location != '' '' ) {
-
var fragmentIndex = location.indexOf('#');
-
var fragment = null;
-
if(fragmentIndex != -1){
-
-
//url 中含有隻相當頁的錨標記
-
fragment = location.substring(fragmentIndex);
-
location = location.substring(0,fragmentIndex);
-
}
-
-
var index = location.indexOf('?');
-
-
if(index != -1) {
-
//url 中已含有其他引數
-
location = location + '&csrftoken=' + token;
-
} else {
-
//url 中沒有其他引數
-
location = location + '?csrftoken=' + token;
-
}
-
if(fragment != null){
-
location += fragment;
-
}
-
-
element.setAttribute(attr, location);
-
}
-
}
在客戶端 html 中,主要是有兩個地方需要加上 token,一個是表單 form,另一個就是連結 a。這段程式碼首先遍歷所有的 form,在 form 最後新增一隱藏欄位,把 csrftoken 放入其中。然後,程式碼遍歷所有的連結標記 a,在其 href 屬性中加入 csrftoken 引數。注意對於 a.href 來說,可能該屬性已經有引數,或者有錨標記。因此需要分情況討論,以不同的格式把 csrftoken 加入其中。
如果你的網站使用 XMLHttpRequest,那麼還需要在 HTTP 頭中自定義 csrftoken 屬性,利用 dojo.xhr 給 XMLHttpRequest 加上自定義屬性程式碼如下:
清單 4. 在 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);
-
};
這裡改寫了 dojo.xhr 的方法,首先確保 dojo.xhr 中存在 HTTP 頭,然後在 args.headers 中新增 csrftoken 欄位,並把 token 值從 session 裡拿出放入欄位中。
PHP程式碼請參考這篇文章淺談CSRF攻擊方式
總結
透過上文分析,目前最便捷的方式是直接判別Referer值,確保同域請求才給放行。如果系統必須支援IE6,那麼就要使用 token 來進行驗證,在大部分情況下,使用 XmlHttpRequest 並不合適,token 只能以引數的形式放於請求之中,若你的系統不支援使用者自己釋出資訊,那這種程度的防護已經足夠,否則的話,你仍然難以防範 token 被駭客竊取並發動攻擊。在這種情況下,你需要小心規劃你網站提供的各種服務,從中間找出那些允許使用者自己釋出資訊的部分,把它們與其他服務分開,使用不同的 token 進行保護,這樣可以有效抵禦駭客對於你關鍵服務的攻擊,把危害降到最低。
如果是開發一個全新的系統,則抵禦 CSRF 的選擇要大得多。筆者建議對於重要的服務,可以儘量使用 XMLHttpRequest 來訪問,這樣增加 token 要容易很多。另外儘量避免在 js 程式碼中使用複雜邏輯來構造常規的同步請求來訪問需要 CSRF 保護的資源,比如 window.location 和 document.createElement(“a”) 之類,這樣也可以減少在附加 token 時產生的不必要的麻煩。
最後,要記住 CSRF 不是駭客唯一的攻擊手段,無論你 CSRF 防範有多麼嚴密,如果你係統有其他安全漏洞,比如跨站域指令碼攻擊 XSS,那麼駭客就可以繞過你的安全防護,展開包括 CSRF 在內的各種攻擊,你的防線將如同虛設。
參考資料
1. 《淺談csrf攻擊方式》
2. 《白帽子講Web安全》
-
維基百科 CSRF,這裡對 CSRF 有一個較為全面地介紹。 http://en.wikipedia.org/wiki/Cross-site_request_forgery
-
開源專案 CSRFGuard,介紹瞭如何使用在 HTTP 請求中加入 token 並驗證的方法來抵禦 CSRF。http://www.owasp.org/index.php/Category:OWASP_CSRFGuard_Project
-
Robust Defenses for Cross-site Request Forgery:分析了一些常用的 CSRF 抵禦方法並獨創性的提出增強瀏覽器的解決方案。
-
developerWorks Web development 專區:透過專門關於 Web 技術的文章和教程,擴充套件您在網站開發方面的技能。
-
developerWorks Ajax 資源中心:這是有關 Ajax 程式設計模型資訊的一站式中心,包括很多文件、教程、論壇、blog、wiki 和新聞。任何 Ajax 的新資訊都能在這裡找到。
-
developerWorks Web 2.0 資源中心,這是有關 Web 2.0 相關資訊的一站式中心,包括大量 Web 2.0 技術文章、教程、下載和相關技術資源。您還可以透過 Web 2.0 新手入門 欄目,迅速瞭解 Web 2.0 的相關概念。
-
檢視 HTML5 專題,瞭解更多和 HTML5 相關的知識和動向。
-
淺談CSRF攻擊方式
-
CSRF 攻擊的應對之道
-
讓我們來談談 CSRF