CSRF(Cross-site request forgery,中文爲跨站請求僞造)是一種利用網站可信用戶的權限去執行未受權的命令的一種惡意攻擊。經過假裝可信用戶的請求來利用信任該用戶的網站,這種攻擊方式雖然不是很流行,可是卻難以防範,其危害也不比其餘安全漏洞小。javascript
本文將簡要介紹CSRF產生的緣由以及利用方式,而後對如何避免這種攻擊方式提供一些可供參考的方案,但願廣大程序猿們都可以對這種攻擊方式有所瞭解,避免本身開發的應用被別人利用。php
CSRF也稱做one-click attack或者session riding,其簡寫有時候也會使用XSRF。html
[TOC]前端
本文將會持續修正和更新,最新內容請參考個人 GITHUB 上的 程序猿成長計劃 項目,歡迎 Star,更多精彩內容請 follow me。java
簡單點說,CSRF攻擊就是 攻擊者利用受害者的身份,以受害者的名義發送惡意請求。與XSS(Cross-site scripting,跨站腳本攻擊)不一樣的是,XSS的目的是獲取用戶的身份信息,攻擊者竊取到的是用戶的身份(session/cookie),而CSRF則是利用用戶當前的身份去作一些未通過受權的操做。jquery
CSRF攻擊最先在2001年被發現,因爲它的請求是從用戶的IP地址發起的,所以在服務器上的web日誌中可能沒法檢測到是否受到了CSRF攻擊,正是因爲它的這種隱蔽性,很長時間以來都沒有被公開的報告出來,直到2007年才真正的被人們所重視。git
CSRF能夠盜用受害者的身份,完成受害者在web瀏覽器有權限進行的任何操做,想一想吧,能作的事情太多了。github
以你的名義發送詐騙郵件,消息web
用你的帳號購買商品ajax
用你的名義完成虛擬貨幣轉帳
泄露我的隱私
...
要完成一個CSRF攻擊,必須具有如下幾個條件:
受害者已經登陸到了目標網站(你的網站)而且沒有退出
受害者有意或者無心的訪問了攻擊者發佈的頁面或者連接地址
(圖片來自網絡,出處不明,百度來的?)
整個步驟大體是這個樣子的:
用戶小明在你的網站A上面登陸了,A返回了一個session ID(使用cookie存儲)
小明的瀏覽器保持着在A網站的登陸狀態,事實上幾乎全部的網站都是這樣作的,通常至少是用戶關閉瀏覽器以前用戶的會話是不會結束的
攻擊者小強給小明發送了一個連接地址,小明打開了這個地址,查看了網頁的內容
小明在打開這個地址的時候,這個頁面已經自動的對網站A發送了一個請求,這時候由於A網站沒有退出,所以只要請求的地址是A的就會攜帶A的cookie信息,也就是使用A與小明之間的會話
這時候A網站確定是不知道這個請求實際上是小強僞造的網頁上發送的,而是誤覺得小明就是要這樣操做,這樣小強就能夠隨意的更改小明在A上的信息,以小明的身份在A網站上進行操做
利用CSRF攻擊,主要包含兩種方式,一種是基於GET請求方式的利用,另外一種是基於POST請求方式的利用。
使用GET請求方式的利用是最簡單的一種利用方式,其隱患的來源主要是因爲在開發系統的時候沒有按照HTTP動詞的正確使用方式來使用形成的。對於GET請求來講,它所發起的請求應該是隻讀的,不容許對網站的任何內容進行修改。
可是事實上並非如此,不少網站在開發的時候,研發人員錯誤的認爲GET/POST的使用區別僅僅是在於發送請求的數據是在Body中仍是在請求地址中,以及請求內容的大小不一樣。對於一些危險的操做好比刪除文章,用戶受權等容許使用GET方式發送請求,在請求參數中加上文章或者用戶的ID,這樣就形成了只要請求地址被調用,數據就會產生修改。
如今假設攻擊者(用戶ID=121)想將本身的身份添加爲網站的管理員,他在網站A上面發了一個帖子,裏面包含一張圖片,其地址爲http://a.com/user/grant_super_user/121
<img src="http://a.com/user/grant_super_user/121" />
設想管理員看到這個帖子的時候,這個圖片確定會自動加載顯示的。因而在管理員不知情的狀況下,一個賦予用戶管理員權限的操做已經悄悄的以他的身份執行了。這時候攻擊者121就獲取到了網站的管理員權限。
相對於GET方式的利用,POST方式的利用更加複雜一些,難度也大了一些。攻擊者須要僞造一個可以自動提交的表單來發送POST請求。
<script> $(function() { $('#CSRF_forCSRFm').trigger('submit'); }); </script> <form action="http://a.com/user/grant_super_user" id="CSRF_form" method="post"> <input name="uid" value="121" type="hidden"> </form>
只要想辦法實現用戶訪問的時候自動提交表單就能夠了。
防範CSRF攻擊,其實本質就是要求網站可以識別出哪些請求是非正經常使用戶主動發起的。這就要求咱們在請求中嵌入一些額外的受權數據,讓網站服務器可以區分出這些未受權的請求,好比說在請求參數中添加一個字段,這個字段的值從登陸用戶的Cookie或者頁面中獲取的(這個字段的值必須對每一個用戶來講是隨機的,不能有規律可循)。攻擊者僞造請求的時候是沒法獲取頁面中與登陸用戶有關的一個隨機值或者用戶當前cookie中的內容的,所以就能夠避免這種攻擊。
令牌同步模式(Synchronizer token pattern,簡稱STP)是在用戶請求的頁面中的全部表單中嵌入一個token,在服務端驗證這個token的技術。token能夠是任意的內容,可是必定要保證沒法被攻擊者猜想到或者查詢到。攻擊者在請求中沒法使用正確的token,所以能夠判斷出未受權的請求。
對於使用Js做爲主要交互技術的網站,將CSRF的token寫入到cookie中
Set-Cookie: CSRF-token=i8XNjC4b8KVok4uw5RftR38Wgp2BFwql; expires=Thu, 23-Jul-2015 10:25:33 GMT; Max-Age=31449600; Path=/
而後使用javascript讀取token的值,在發送http請求的時候將其做爲請求的header
X-CSRF-Token: i8XNjC4b8KVok4uw5RftR38Wgp2BFwql
最後服務器驗證請求頭中的token是否合法。
使用驗證碼能夠杜絕CSRF攻擊,可是這種方式要求每一個請求都輸入一個驗證碼,顯然沒有哪一個網站願意使用這種粗暴的方式,用戶體驗太差,用戶會瘋掉的。
首先在index.php中,建立一個表單,在表單中,咱們將session中存儲的token放入到隱藏域,這樣,表單提交的時候token會隨表單一塊兒提交
<?php $token = sha1(uniqid(rand(), true)); $_SESSION['token'] = $token; ?> <form action="buy.php" method="post"> <input type="hidden" name="token" value="<?=$token; ?>" /> ... 表單內容 </form>
在服務端校驗請求參數的buy.php
中,對錶單提交過來的token與session中存儲的token進行比對,若是一致說明token是有效的
<?php if ($_POST['token'] != $_SESSION['token']) { // TOKEN無效 throw new \Exception('Token無效,請求爲僞造請求'); } // TOKEN有效,表單內容處理
對於攻擊者來講,在僞造請求的時候是沒法獲取到用戶頁面中的這個token
值的,所以就能夠識別出其建立的僞造請求。
在Laravel框架中,使用了VerifyCSRFToken
這個中間件來防範CSRF攻擊。
在頁面的表單中使用{{ CSRF_field() }}
來生成token,該函數會在表單中添加一個名爲_token
的隱藏域,該隱藏域的值爲Laravel生成的token,Laravel使用隨機生成的40個字符做爲防範CSRF攻擊的token。
$this->put('_token', Str::random(40));
若是請求是ajax異步請求,能夠在meta
標籤中添加token
<meta name="CSRF-token" content="{{ CSRF_token() }}">
使用jquery
做爲前端的框架時候,能夠經過如下配置將該值添加到全部的異步請求頭中
$.ajaxSetup({ headers: { 'X-CSRF-TOKEN': $('meta[name="CSRF-token"]').attr('content') } });
在啓用session的時候,Laravel會生成一個名爲_token
的值存儲到session中。而使用前面兩種方式在頁面中加入的token就是使用的這一個值。在用戶請求到來時,VerifyCSRFToken
中間件會對符合條件的請求進行CSRF檢查
if ( $this->isReading($request) || $this->runningUnitTests() || $this->shouldPassThrough($request) || $this->tokensMatch($request) ) { return $this->addCookieToResponse($request, $next($request)); } throw new TokenMismatchException;
在if
語句中有四個條件,只要任何一個條件結果爲true
則任何該請求是合法的,不然就會拋出TokenMismatchException
異常,告訴用戶請求不合法,存在CSRF攻擊。
第一個條件$this->isReading($request)
用來檢查請求是否會對數據產生修改
protected function isReading($request) { return in_array($request->method(), ['HEAD', 'GET', 'OPTIONS']); }
這裏判斷了請求方式,若是是HEAD
,GET
,OPTIONS
這三種請求方式則直接放行。你可能會感到疑惑,爲何GET請求也要放行呢?這是由於Laravel認爲這三個請求都是請求查詢數據的,若是一個請求是使用GET方式,那不管請求多少次,不管請求參數如何,都不該該最數據作任何修改。
第二個條件顧名思義是對單元測試進行放行,第三個是爲開發者提供了一個能夠對某些請求添加例外的功能,最後一個$this->tokensMatch($request)
則是真正起做用的一個,它是Laravel防範CSRF攻擊的關鍵
$sessionToken = $request->session()->token(); $token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN'); if (! $token && $header = $request->header('X-XSRF-TOKEN')) { $token = $this->encrypter->decrypt($header); } if (! is_string($sessionToken) || ! is_string($token)) { return false; } return hash_equals($sessionToken, $token);
Laravel會從請求中讀取_token
參數的的值,這個值就是在前面表單中添加的CSRF_field()
函數生成的。若是請求是異步的,那麼會讀取X-CSRF-TOKEN
請求頭,從請求頭中讀取token的值。
最後使用hash_equals
函數驗證請求參數中提供的token值和session中存儲的token值是否一致,若是一致則說明請求是合法的。
你可能注意到,這個檢查過程當中也會讀取一個名爲X-XSRF-TOKEN
的請求頭,這個值是爲了提供對一些javascript框架的支持(好比Angular),它們會自動的對異步請求中添加該請求頭,而該值是從Cookie中的XSRF-TOKEN
中讀取的,所以在每一個請求結束的時候,Laravel會發送給客戶端一個名爲XSRF-TOKEN
的Cookie值
$response->headers->setCookie( new Cookie( 'XSRF-TOKEN', $request->session()->token(), time() + 60 * $config['lifetime'], $config['path'], $config['domain'], $config['secure'], false ) );
本文只是對CSRF作了一個簡單的介紹,主要是側重於CSRF是什麼以及如何應對CSRF攻擊。有一個事實是咱們沒法迴避的:沒有絕對安全的系統,你有一千種防護對策,攻擊者就有一千零一種攻擊方式,但無論如何,咱們都要盡最大的努力去將攻擊者攔截在門外。若是但願深刻了解如何發起一個CSRF攻擊,能夠參考一下這篇文章 從零開始學CSRF。
做爲一名web方向的研發人員,不管你是從事業務邏輯開發仍是作單純的技術研究,瞭解一些安全方面的知識都是頗有必要的,多關注一些安全方向的動態,瞭解常見的攻擊方式以及應對策略,必將在你成長爲一名大牛的路上爲你「推波助瀾」。
本文將會持續修正和更新,最新內容請參考個人 GITHUB 上的 程序猿成長計劃 項目,歡迎 Star,更多精彩內容請 follow me。