CSRF攻擊與防禦
1、簡介
CSRF的全名為Cross-site request forgery,它的中文名為 跨站請求偽造(偽造跨站請求【這樣讀順口一點】)
CSRF是一種夾持用戶在已經登陸的web應用程序上執行非本意的操作的攻擊方式。相比於XSS,CSRF是利用了系統對頁面瀏覽器的信任,XSS則利用了系統對用戶的信任。
2、CSRF攻擊原理
下面為CSRF攻擊原理圖:
由上圖分析我們可以知道構成CSRF攻擊是有條件的:
1、客戶端必須一個網站並生成cookie憑證存儲在瀏覽器中
2、該cookie沒有清除,客戶端又tab一個頁面進行訪問別的網站
3、CSRF例子與分析
我們就以遊戲虛擬幣轉賬為例子進行分析
3.1、簡單級別CSRF攻擊
假設某遊戲網站的虛擬幣轉賬是采用GET方式進行操作的,樣式如:
1 http://www.game.com/Transfer.php?toUserId=11&vMoney=1000
此時惡意攻擊者的網站也構建一個相似的鏈接:
1、可以是采用圖片隱藏,頁面一打開就自動進行訪問第三方文章:<img src=‘攻擊鏈接‘>
2、也可以采用js進行相應的操作
http://www.game.com/Transfer.php?toUserId=20&vMoney=1000 #toUserID為攻擊的賬號ID
1、假若客戶端已經驗證並登陸www.game.com網站,此時客戶端瀏覽器保存了遊戲網站的驗證cookie
2、客戶端再tab另一個頁面進行訪問惡意攻擊者的網站,並從惡意攻擊者的網站構造的鏈接來訪問遊戲網站
3、瀏覽器將會攜帶該遊戲網站的cookie進行訪問,刷一下就沒了1000遊戲虛擬幣
3.2、中級別CSRF攻擊
遊戲網站負責人認識到了有被攻擊的漏洞,將進行升級改進。
將由鏈接GET提交數據改成了表單提交數據
toUserId: vMoney:
Transfer.php
1 <?php2 session_start();3 if (isset($_REQUEST[‘toUserId‘] && isset($_REQUEST[‘vMoney‘])) #驗證4 {5 //相應的轉賬操作6 }7 ?>
惡意攻擊者將會觀察網站的表單形式,並進行相應的測試。
首先惡意攻擊者采用(http://www.game.com/Transfer.php?toUserId=20&vMoney=1000)進行測試,發現仍然可以轉賬。
那麽此時遊戲網站所做的更改沒起到任何的防範作用,惡意攻擊者只需要像上面那樣進行攻擊即可達到目的。
總結:
1、網站開發者的錯誤點在於沒有使用$_POST進行接收數據。當$_REQUEST可以接收POST和GET發來的數據,因此漏洞就產生了。
3.3、高級別CSRF攻擊
這一次,遊戲網站開發者又再一次認識到了錯誤,將進行下一步的改進與升級,將采用POST來接收數據
Transfer.php
1 <?php2 session_start();3 if (isset($_POST[‘toUserId‘] && isset($_POST[‘vMoney‘])) #驗證4 {5 //相應的轉賬操作6 }7 ?>
此時惡意攻擊者就沒有辦法進行攻擊了麽?那是不可能的。
惡意攻擊者根據遊戲虛擬幣轉賬表單進行偽造了一份一模一樣的轉賬表單,並且嵌入到iframe中
嵌套頁面:(用戶訪問惡意攻擊者主機的頁面,即tab的新頁面)
<!DOCTYPE html><html><head> <meta charset="utf-8"> <title>攻擊者主機頁面</title> <script type="text/javascript"> function csrf() { window.frames[‘steal‘].document.forms[0].submit(); } </script></head><body onload="csrf()"><iframe name="steal" display="none" src="./xsrf.html"></iframe></body></html>
表單頁面:(csrf.html)
<!DOCTYPE html><html><head> <title>csrf</title></head><body><form display="none" action="http://www.game.com/Transfer.php" method="post" > <input type="hidden" name="toUserID" value="20"> <input type="hidden" name="vMoney" value="1000"></form></body></html>
客戶端訪問惡意攻擊者的頁面一樣會遭受攻擊。
總結:
CSRF攻擊是源於Web的隱式身份驗證機制!Web的身份驗證機制雖然可以保證一個請求是來自於某個用戶的瀏覽器,但卻無法保證該請求是用戶批準發送的
4、CSRF防禦方法
服務器端防禦:
1、重要數據交互采用POST進行接收,當然是用POST也不是萬能的,偽造一個form表單即可破解
2、使用驗證碼,只要是涉及到數據交互就先進行驗證碼驗證,這個方法可以完全解決CSRF。但是出於用戶體驗考慮,網站不能給所有的操作都加上驗證碼。因此驗證碼只能作為一種輔助手段,不能作為主要解決方案。
3、驗證HTTP Referer字段,該字段記錄了此次HTTP請求的來源地址,最常見的應用是圖片防盜鏈。PHP中可以采用APache URL重寫規則進行防禦,可參考:http://www.cnblogs.com/phpstudy2015-6/p/6715892.html
4、為每個表單添加令牌token並驗證
(可以使用cookie或者session進行構造。當然這個token僅僅只是針對CSRF攻擊,在這前提需要解決好XSS攻擊,否則這裏也將會是白忙一場【XSS可以偷取客戶端的cookie】)
CSRF攻擊之所以能夠成功,是因為攻擊者可以偽造用戶的請求,該請求中所有的用戶驗證信息都存在於Cookie中,因此攻擊者可以在不知道這些驗證信息的情況下直接利用用戶自己的Cookie來通過安全驗證。由此可知,抵禦CSRF攻擊的關鍵在於:在請求中放入攻擊者所不能偽造的信息,並且該信息不存在於Cookie之中。
鑒於此,我們將為每一個表單生成一個隨機數秘鑰,並在服務器端建立一個攔截器來驗證這個token,如果請求中沒有token或者token內容不正確,則認為可能是CSRF攻擊而拒絕該請求。
由於這個token是隨機不可預測的並且是隱藏看不見的,因此惡意攻擊者就不能夠偽造這個表單進行CSRF攻擊了。
要求:
1、要確保同一頁面中每個表單都含有自己唯一的令牌
2、驗證後需要刪除相應的隨機數
構造令牌類Token.calss.php
1 <?php 2 class Token 3 { 4 /** 5 * @desc 獲取隨機數 6 * 7 * @return string 返回隨機數字符串 8 */ 9 private function getTokenValue()10 {11 return md5(uniqid(rand(), true).time());12 }13 14 /**15 * @desc 獲取秘鑰16 *17 * @param $tokenName string | 與秘鑰值配對成鍵值對存入session中(標識符,保證唯一性)18 *19 * @return array 返回存儲在session中秘鑰值20 */21 public function getToken($tokenName)22 {23 $token[‘name‘]=$tokenName; #先將$tokenName放入數組中24 session_start();25 if(@$_SESSION[$tokenName]) #判斷該用戶是否存儲了該session26 { #是,則直接返回已經存儲的秘鑰27 $token[‘value‘]=$_SESSION[$tokenName];28 return $token;29 }30 else #否,則生成秘鑰並保存31 {32 $token[‘value‘]=$this->getTokenValue();33 $_SESSION[$tokenName]=$token[‘value‘];34 return $token;35 }36 }37 38 }39 #測試40 $csrf=new Token();41 $name=‘form1‘;42 $a=$csrf->getToken($name);43 echo "<pre>";44 print_r($a);45 echo "</pre>";46 echo "<pre>";47 print_r($_SESSION);48 echo "</pre>";die;49 50 ?>
表單中使用:
1 <?php 2 session_start(); 3 include(”Token.class.php”); 4 $token=new Token(); 5 $arr=$token->getToken(‘transfer’); #保證唯一性(標識符) 6 ?> 7 <form method=”POST” action=”./transfer.php”> 8 <input type=”text” name=”toUserId”> 9 <input type=”text” name=”vMoney”>10 <input type="hidden" name="<?php echo $arr[‘name‘] ?>" value="<?php echo $arr[‘value‘]?>" >11 <input type=”submit” name=”submit” value=”Submit”>12 </from>
驗證:
1 <?php 2 #轉賬表單驗證 3 session_start(); 4 if($_POST[‘transfer‘]==$_SESSION[‘transfer‘]) #先檢驗秘鑰 5 { 6 unset($_SESSION[‘transfer‘]); #刪除已經檢驗的存儲秘鑰 7 if ( &&isset($_POST[‘toUserId‘] && isset($_POST[‘vMoney‘])) #驗證 8 { 9 //相應的轉賬操作10 }11 }12 else13 {14 return false;15 }16 ?>
1 <?php 2 #轉賬表單驗證 3 session_start(); 4 if($_POST[‘transfer‘]==$_SESSION[‘transfer‘]) #先檢驗秘鑰 5 { 6 unset($_SESSION[‘transfer‘]); #刪除已經檢驗的存儲秘鑰 7 if ( &&isset($_POST[‘toUserId‘] && isset($_POST[‘vMoney‘])) #驗證 8 { 9 //相應的轉賬操作10 }11 }12 else13 {14 return false;15 }16 ?>
該方法套路:
1. 用戶訪問某個表單頁面。
2. 服務端生成一個Token,放在用戶的Session中,或者瀏覽器的Cookie中。【這裏已經不考慮XSS攻擊】
3. 在頁面表單附帶上Token參數。
4. 用戶提交請求後, 服務端驗證表單中的Token是否與用戶Session(或Cookies)中的Token一致,一致為合法請求,不是則非法請求。
CSRF攻擊與防禦