PHP開發中csrf攻擊的簡單演示和防範

 

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,這個頁面腳本如下:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

<?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('更新失敗');

}

正常情況下這個頁面的鏈接是放在站點A下面的,比如A站點的csrfdemo.php頁面,用戶登錄站點A以後可以通過點擊這個鏈接來發送請求,比如站點A有一個頁面腳本,包含了這個鏈接:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

<?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腳本代碼:

1

2

3

<?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增加些代碼: 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

<?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代碼如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

<?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等等,不過這些我還沒研究過,後續再說吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章