CSRF的全名為Cross-site request forgery,它的中文名為跨站請求偽造(偽造跨站請求【這樣讀順口一點】)CSRF是一種夾持用戶在已經登陸的web應用程式上執行非本意的操作的攻擊方式。相較於XSS,CSRF是利用了系統對頁面瀏覽器的信任,XSS則利用了系統對使用者的信任。
csrf攻擊,即cross site request forgery跨站(域名)請求偽造,這裡的forgery就是偽造的意思。網路上有很多關於csrf的介紹,例如一位前輩的文章CSRF的攻擊方式詳解,參考這篇文章簡單解釋下:csrf 攻擊能夠實現依賴於這樣一個簡單的事實:我們在用瀏覽器瀏覽網頁時通常會開啟好幾個瀏覽器標籤(或視窗),如果我們登入了一個網站A,網站A如果是透過cookie來追蹤使用者的會話,那麼在使用者登入了網站A之後,網站A就會在使用者的客戶端設置cookie,假如網站A有一個頁面siteA-page.php(url資源)被網站B知道了url位址,而這個頁面的位址以某種方式被嵌入到了B網站的一個頁面siteB-page.php中,如果這時使用者在維持A網站會話的同時開啟了B網站的siteB-page.php,那麼只要siteB-page.php頁面可以觸發這個url位址(請求A網站的url資源)就實現了csrf攻擊。
上面的解釋很拗口,下面舉個簡單的例子來示範下。
1,背景和正常的請求流程
A網站網域為html5.yang.com,它有一個/get-update.php?uid=uid&username=username位址,可以看到這個位址可以透過get方法傳遞一些參數,假如這個頁面的邏輯是:它透過判斷uid是否合法來更新username,這個頁面腳本如下:
# #
<?php // 这里简便起见, 从data.json中取出数据代替请求数据库 $str = file_get_contents('data.json'); $data = json_decode($str, true); // 检查cookie和请求更改的uid, 实际应检查数据库中的用户是否存在 empty($_COOKIE['uid']) ||empty($_GET['uid']) || $_GET['uid'] != $data['id'] ? die('非法用户') : ''; // 检查username参数 $data['username'] = empty($_GET['username']) ? die('用户名不能为空') : $_GET['username']; // 更新数据 $data['username'] = $_GET['username']; if(file_put_contents('data.json', json_encode($data))) { echo "用户名已更改为{$data['username']}<br>"; } else { die('更新失败'); }
<?php // 这里用一个data.json文件保存用户数据,模拟数据库中的数据 // 先初始化data.json中的数据为{"id":101,"username":"jack"}, 注意这句只让它执行一次, 然后把它注释掉 // file_put_contents('data.json','{"id":101,"username":"jack"}'); $data = json_decode(file_get_contents('data.json'), true); // 这里为了简便, 省略了用户身份验证的过程 if ($data['username']) { // 设置cookie setcookie('uid', $data['id'], 0); echo "登录成功, {$data['username']}<br>"; } ?> <a href="http://html5.yang.com/csrfdemo/get-update.php?uid=101&username=json" rel="external nofollow" > 更新用户名为json </a>
# #用點擊頁面中的連結來到get-update.php頁面:
#上面是正常的請求流程,下面來看B站點是如何實作csrf攻擊的。
2,csrf攻擊的最簡單實作
B網站網域為test.yang.com,它有一個頁面csrf.php,只要用戶在維持A網站會話的同時打開了這個頁面,那麼B站點就可以實現csrf攻擊。至於為什麼會打開......,其實這種情景在我們瀏覽網頁時是很常見的,比如我在寫這篇博客時,寫著寫著感覺對csrf某個地方不懂,然後就百度了,結果百度出來好多結果,假如說有個網站叫csrf百科知識,這個網站對csrf介紹的非常詳細、非常權威,那麼我很可能會點進去看,但是這個網站其實是個釣魚網站,它在某在瀏覽頻率很高的頁面中嵌入了我部落格編輯頁面的url位址,那麼它就可以實現對我部落格的csrf攻擊。好了,言歸正傳,下面來看下csrf.php腳本程式碼:
<?php ?> <img src="http://html5.yang.com/csrfdemo/get-update.php?uid=101&username=jsonp">
可以看到上面的程式碼沒有php程式碼,只有一個img標籤,img標籤的src就是A站點的那個更新用戶名的鏈接,只不過把username改為了jsonp,訪問站點B的csrf.php這個頁面:
下面再來訪問下A網站的csrfdemo.php頁面:
可以看到使用者名稱被修改為了jsonp。
簡單分析下:B網站的這個csrf.php利用了html中的img標籤,我們都知道img標籤有個src屬性,屬性值指向需要載入的圖片位址,當頁面載入時,載入圖片就相當於向src指向的位址發動http請求,只要把圖片的位址修改為某個腳本位址,這樣自然就實現了最簡單的csrf攻擊。如此說來,其實csrf很容易實現,只不過大家都是“正人君子”,誰沒事會閒著去做這種“下三濫”的事情。但是害人之心不可有,防人之心不可無。下面看如何簡單防範這種最簡單的csrf攻擊。
3,簡單防範措施
其實防範措施也比較簡單,A網站可以在get-update.php腳本中判斷請求頭的來源,如果來源不是A網站就可以截斷請求,以下在get-update.php增加些程式碼:
<?php // 检查上一页面是否为当前站点下的页面 if (!empty($_SERVER['HTTP_REFERER'])) { if (parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST) != 'html5.yang.com') { // 可以设置http错误码或者指向一个无害的url地址 //header('HTTP/1.1 404 not found'); //header('HTTP/1.1 403 forbiden'); header('Location: http://html5.yang.com/favicon.ico'); // 这里需要注意一定要exit(), 否则脚本会接着执行 exit(); } } $str = file_get_contents('data.json'); // 代码省略
但是,这样就万事大吉了吗,如果http请求头被伪造了呢?A站点升级了防御,B站点同时也可以升级攻击,通过curl请求来实现csrf,修改B站点的csrf.php代码如下:
<?php $url = 'http://html5.yang.com/csrfdemo/get-update.php?uid=101&username=jsonp'; $refer = 'http://html5.yang.com/'; // curl方法发起csrf攻击 $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); // 设置Referer curl_setopt($ch, CURLOPT_REFERER, $refer); // 这里需要携带上cookie, 因为A站点get-update.php对cooke进行了判断 curl_setopt($ch, CURLOPT_COOKIE, 'uid=101'); curl_exec($ch); curl_close($ch); ?> <img src="http://html5.yang.com/csrfdemo/get-update.php?uid=101&username=jsonp">
这样同样可以实现csrf攻击的目的。那么就没有比较好的防范方法了吗?
4,小结
下面我们回到问题的开始,站点A通过cookie来跟踪用户会话,在cookie中存放了重要的用户信息uid,get-update.php脚本通过判断用户的cookie正确与否来决定是否更改用户信息,看来靠cookie来跟踪会话并控制业务逻辑是不太安全的,还有最严重的一点:get-update.php通过get请求来修改用户信息,这个是大忌。所以站点A可以接着升级防御:用session来代替cookie来跟踪用户会话信息,将修改用户信息的逻辑重写,只允许用post方法来请求用户信息。站点B同样可以升级攻击:curl可以构造post请求,劫持session等等,不过这些我还没研究过,后续再说吧。
php curl带有csrf-token验证模拟提交实例详解
以上是PHP開發中csrf攻擊的示範與防範詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!