程序猿必讀-防範CSRF跨站請求僞造 頂 原

DSC00964

CSRF(Cross-site request forgery,中文爲跨站請求僞造)是一種利用網站可信用戶的權限去執行未授權的命令的一種惡意攻擊。通過僞裝可信用戶的請求來利用信任該用戶的網站,這種攻擊方式雖然不是很流行,但是卻難以防範,其危害也不比其他安全漏洞小。

本文將簡要介紹CSRF產生的原因以及利用方式,然後對如何避免這種攻擊方式提供一些可供參考的方案,希望廣大程序猿們都能夠對這種攻擊方式有所瞭解,避免自己開發的應用被別人利用。

CSRF也稱作one-click attack或者session riding,其簡寫有時候也會使用XSRF

[TOC]

本文將會持續修正和更新,最新內容請參考我的 GITHUB 上的 程序猿成長計劃 項目,歡迎 Star,更多精彩內容請 follow me

什麼是CSRF?

簡單點說,CSRF攻擊就是 攻擊者利用受害者的身份,以受害者的名義發送惡意請求。與XSS(Cross-site scripting,跨站腳本攻擊)不同的是,XSS的目的是獲取用戶的身份信息,攻擊者竊取到的是用戶的身份(session/cookie),而CSRF則是利用用戶當前的身份去做一些未經過授權的操作。

CSRF攻擊最早在2001年被發現,由於它的請求是從用戶的IP地址發起的,因此在服務器上的web日誌中可能無法檢測到是否受到了CSRF攻擊,正是由於它的這種隱蔽性,很長時間以來都沒有被公開的報告出來,直到2007年才真正的被人們所重視。

CSRF有哪些危害

CSRF可以盜用受害者的身份,完成受害者在web瀏覽器有權限進行的任何操作,想想吧,能做的事情太多了。

  • 以你的名義發送詐騙郵件,消息
  • 用你的賬號購買商品
  • 用你的名義完成虛擬貨幣轉賬
  • 泄露個人隱私
  • ...

產生原理以及利用方式

要完成一個CSRF攻擊,必須具備以下幾個條件:

  • 受害者已經登錄到了目標網站(你的網站)並且沒有退出
  • 受害者有意或者無意的訪問了攻擊者發佈的頁面或者鏈接地址

(圖片來自網絡,出處不明,百度來的😂)

整個步驟大致是這個樣子的:

  1. 用戶小明在你的網站A上面登錄了,A返回了一個session ID(使用cookie存儲)
  2. 小明的瀏覽器保持着在A網站的登錄狀態,事實上幾乎所有的網站都是這樣做的,一般至少是用戶關閉瀏覽器之前用戶的會話是不會結束的
  3. 攻擊者小強給小明發送了一個鏈接地址,小明打開了這個地址,查看了網頁的內容
  4. 小明在打開這個地址的時候,這個頁面已經自動的對網站A發送了一個請求,這時候因爲A網站沒有退出,因此只要請求的地址是A的就會攜帶A的cookie信息,也就是使用A與小明之間的會話
  5. 這時候A網站肯定是不知道這個請求其實是小強僞造的網頁上發送的,而是誤以爲小明就是要這樣操作,這樣小強就可以隨意的更改小明在A上的信息,以小明的身份在A網站上進行操作

利用方式

利用CSRF攻擊,主要包含兩種方式,一種是基於GET請求方式的利用,另一種是基於POST請求方式的利用。

GET請求利用

使用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就獲取到了網站的管理員權限。

POST請求利用

相對於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

令牌同步模式(Synchronizer token pattern,簡稱STP)是在用戶請求的頁面中的所有表單中嵌入一個token,在服務端驗證這個token的技術。token可以是任意的內容,但是一定要保證無法被攻擊者猜測到或者查詢到。攻擊者在請求中無法使用正確的token,因此可以判斷出未授權的請求。

Cookie-to-Header 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攻擊,但是這種方式要求每個請求都輸入一個驗證碼,顯然沒有哪個網站願意使用這種粗暴的方式,用戶體驗太差,用戶會瘋掉的。

簡單實現STP

首先在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中間件

在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']);
}

這裏判斷了請求方式,如果是HEADGETOPTIONS這三種請求方式則直接放行。你可能會感到疑惑,爲什麼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

參考

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