CSRF 攻擊 及其 Laravel 解決

arkssss發表於2020-04-26

CSRF 保護

Laravel 可以輕鬆使地保護你的應用程式免受 跨站請求偽造 (CSRF) 攻擊。 跨站點請求偽造是一種惡意攻擊,它憑藉已透過身份驗證的使用者身份來執行未經過授權的命令。

Laravel 會自動為每個活躍的使用者的會話生成一個 CSRF「令牌」。該令牌用於驗證經過身份驗證的使用者是否是嚮應用程式發出請求的使用者。

簡而言之,一般的 web app 會在請求的時候在 cookiessession 中表示出此次請求的會話 id, 每次請求的時候客戶端都會向服務端傳送此會話id來標示此次會話。但是 sessionID 僅能標示出此次回話的存在,只靠 sessionID 並不能識別出此次的請求是不是真的出自於該客戶端使用者的操作,此就造成了 CSRF 攻擊的風險。

一般解決思路

我們的 web 請求的時候,需要向服務端證實本次請求確實是來自於使用者,具體的解決思路如下。在服務端返回給客戶端頁面的時候,生成一個完全隨機的 CSRF_TOKEN ,同時儲存於服務端和客戶端。在客戶端對服務端進行請求的時候,將此 CSRF_TOKEN 傳送於服務端,服務端對此 TOKEN 進行驗證。如果對比一致,那麼請求執行,同時 服務端和客戶端重新整理 CSRF_TOKEN, 用於下一次請求。

一般 CSRF 攻擊目的在於代替被攻擊使用者身份修改資訊,所以:各種 ’寫請求‘ 都需要加上一個 CSRF_TOKEN 保證安全性,而 ‘’讀請求’ ‘就可以不進行 CSRF_TOKEN 驗證:

/**
* Laravel 中驗證邏輯,如果是 head, get, option 這三個 '讀請求' 便可以跳過認證
* Determine if the HTTP request uses a ‘read’ verb.
*
* @param  \Illuminate\Http\Request  $request
* @return bool
*/
protected function isReading($request)
{
 return in_array($request->method(), ['HEAD', 'GET', 'OPTIONS']);
}

CSRF_TOKEN 存在於客戶端的形式可以有多種

  • Cookies 中儲存

  • Meta 標籤中

實際上 Laravel 會同時在 cookiesmeta 標籤中儲存當前會話的 CSRS_TOKEN

Cookies 的 CSRF_TOKEN 儲存

Laravel 裡 cookies 的 CSRF_TOKEN 儲存規定於 \App\Http\Middleware\VerifyCsrfToken 這個中介軟體中:

<?php
​
namespace App\Http\Middleware;
​
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;class VerifyCsrfToken extends Middleware
{
 /**
 * addHttpCookies為true則會在請求返回的時候對客戶端設定 CSRF_TOKEN 的 cookies
 * Indicates whether the XSRF-TOKEN cookie should be set on the response.
 *
 * @var bool
 */
 protected $addHttpCookie = true;/**
 * The URIs that should be excluded from CSRF verification.
 *
 * @var array
 */
 protected $except = [
 //
 ];
}

如果設定了 cookies 中的 token, 那麼在每一次請求的時候,都會在 request header 中附加上該資訊, 但是 cookies 也有被客戶端禁用的情況。

注意,laravel 中 cookies 設定的 csrf_token 並不是存在於服務端的原始值,而是經過一次加密的 csrf_token, 需要在傳回服務端時進行解密比較。且由於 random byte 的存在,相同的 csrf_token 會帶來不同的密文, 具體的加密解密操作可見 VerifyCsrfToken 中注入的 Encrypter

/* DI in VerifyCsrfToken */
public function __construct(Application $app, Encrypter $encrypter)
{
 $this->app = $app;
 $this->encrypter = $encrypter;
}

Meta 標籤的 CSRF_TOKEN 儲存

為了應對 cookies 禁用以及其他 cookies 使用不成功的時候,我們還可以在 <meta> 標籤中設定此次會話的 CSRF_TOKEN

<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">

Laravel 獲取 request中的 CSRF_TOKEN

Laravel 會從 三個渠道獲得 CSRF_TOKEN

  • post 請求的 input 輸出欄 (一般為隱藏欄)

  • request header 中的 X-CSRF-TOKEN 欄位

  • 如果以上兩種都不存在,則在 cookies 中尋找,且進行解密操作還原為服務端的原始值

以下原始碼可以看出 laravel 對請求中的 csrf_token 的獲取順序:

    /**
    * Get the CSRF token from the request.
    *
    * @param  \Illuminate\Http\Request  $request
    * @return string
    */
    protected function getTokenFromRequest($request)
    {
     $token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');if (! $token && $header = $request->header('X-XSRF-TOKEN')) {
     $token = $this->encrypter->decrypt($header, static::serialized());
     }return $token;
    }

服務端比較CSRF_TOKEN

可以看到,在服務端,csrf_token 存於 $request->session()->token() 中。

laravel 中使用 hash_equals 進行兩者的比較,可以使得無論兩者值是什麼,比較的時間都是一樣從而有效的防止了 側通道攻擊

/**
* Determine if the session and input CSRF tokens match.
*
* @param  \Illuminate\Http\Request  $request
* @return bool
*/
protected function tokensMatch($request)
{
 $token = $this->getTokenFromRequest($request);return is_string($request->session()->token()) &&
 is_string($token) &&
 hash_equals($request->session()->token(), $token);
}

如何傳遞 CSRC_TOKEN

  • Method 1: 現階段,對於每一次 post 請求最好從 meta 標籤中獲取一個 csrf_token 作為其隱藏表單域名,name 設定為 _token。從而保證 laravel 在獲得 csrf_token 的時候擁有最高的效率(因為其預設先獲取 input 域中的 _token)
    /* example for post request */
    axios.post('/login', {
     '_token' : $('meta[name="csrf-token"]').attr('content'),
     'email' : values.email,
     'password' : values.password,
     'remember' : values.remember,
    }).then((res)=>{
     console.log(res);
    }).catch(()=>{
     console.log('error');
    });
  • Method 2: 也可以在每一次請求的時候,給請求頭賦值 X-CSRF_TOKEN
    $.ajaxSetup({
     headers: {
     'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
     }
    });
  • Method 3: 當然在有 cookies 的 XSRF-TOKEN 欄位情況下,可以什麼都不用管直接傳送請求,但這種方式較前兩種最慢。
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章