跳到主要內容

CSRF 保護

📝 此頁面為 Laravel 官方文檔的繁體中文翻譯。查看原始英文版本

CSRF 保護

簡介

跨站請求偽造(CSRF)是一類惡意攻擊,攻擊者利用此漏洞以已驗證使用者的名義執行未授權的命令。幸運的是,Laravel 讓您能輕鬆保護應用程式免受跨站請求偽造(CSRF)攻擊。

漏洞說明

如果您不熟悉跨站請求偽造,讓我們討論一個範例來說明如何利用此漏洞。假設您的應用程式有一個 /user/email 路由,該路由接受一個 POST 請求來變更已驗證使用者的電子郵件地址。此路由通常會期望一個 email 輸入欄位包含使用者想要開始使用的電子郵件地址。

如果沒有 CSRF 保護,惡意網站可以建立一個 HTML 表單,指向您應用程式的 /user/email 路由,並提交惡意使用者自己的電子郵件地址:

<form action="https://your-application.com/user/email" method="POST">
    <input type="email" value="malicious-email@example.com">
</form>

<script>
    document.forms[0].submit();
</script>

如果惡意網站在頁面載入時自動提交表單,惡意使用者只需要誘騙您應用程式的一個毫無戒心的使用者拜訪他們的網站,該使用者的電子郵件地址就會在您的應用程式中被變更。

為了防止此漏洞,我們需要檢查每個傳入的 POSTPUTPATCHDELETE 請求中的機密 session 值,而惡意應用程式無法存取此值。

防止 CSRF 請求

Illuminate\Foundation\Http\Middleware\PreventRequestForgery 中介層預設包含在 web 中介層群組中,它使用雙層方法來保護您的應用程式免受跨站請求偽造攻擊。

首先,中介層會檢查瀏覽器的 Sec-Fetch-Site 標頭。現代瀏覽器會自動在每個請求上設定此標頭,指示該請求是來自相同來源、相同網站還是跨站來源。如果標頭指示請求來自相同來源,則請求會立即被允許,無需任何令牌驗證。

如果來源驗證未通過 — 例如,因為請求來自較舊的瀏覽器,該瀏覽器不會傳送 Sec-Fetch-Site 標頭,或因為連線不安全 — 中介層會回退到傳統的 CSRF 令牌驗證。

Laravel 會自動為應用程式管理的每個活躍使用者 session 生成一個 CSRF「令牌」。此令牌用於驗證已驗證的使用者是否為實際向應用程式發出請求的人。由於此令牌儲存在使用者的 session 中,並且每次 session 重新生成時都會變更,惡意應用程式無法存取它。

可以透過請求的 session 或 csrf_token 輔助函數來存取當前 session 的 CSRF 令牌:

use Illuminate\Http\Request;

Route::get('/token', function (Request $request) {
    $token = $request->session()->token();

    $token = csrf_token();

    // ...
});

每當在應用程式中定義「POST」、「PUT」、「PATCH」或「DELETE」HTML 表單時,都應該在表單中包含一個隱藏的 CSRF _token 欄位,以便 CSRF 保護中介層可以驗證請求。為了方便起見,您可以使用 @csrf Blade 指令來生成隱藏的令牌輸入欄位:

<form method="POST" action="/profile">
    @csrf

    <!-- Equivalent to... -->
    <input type="hidden" name="_token" value="{{ csrf_token() }}" />
</form>

CSRF 令牌與 SPA

如果您正在建立一個使用 Laravel 作為 API 後端的 SPA,應參閱 Laravel Sanctum 文件以了解如何進行 API 認證和防止 CSRF 漏洞。

來源驗證

如上所述,Laravel 的請求偽造中介層首先檢查 Sec-Fetch-Site 標頭以確定請求是否來自相同來源。預設情況下,如果此檢查未通過,中介層會回退到 CSRF 令牌驗證。

但是,如果您希望僅依賴來源驗證並完全停用 CSRF 令牌回退,可以在應用程式的 bootstrap/app.php 檔案中使用 preventRequestForgery 方法來實現:

->withMiddleware(function (Middleware $middleware): void {
    $middleware->preventRequestForgery(originOnly: true);
})

使用僅限來源模式時,未通過來源驗證的請求將收到 403 HTTP 響應,而不是通常與 CSRF 令牌不匹配相關的 419 響應。

[!WARNING] Sec-Fetch-Site 標頭僅由瀏覽器透過安全(HTTPS)連線傳送。如果您的應用程式未透過 HTTPS 提供服務,來源驗證將不可用,中介層將回退到 CSRF 令牌驗證。

如果您的應用程式需要接受來自子網域的請求(例如,dashboard.example.com 接受來自 example.com 的請求),除了相同來源請求外,您還可以允許相同網站的請求:

->withMiddleware(function (Middleware $middleware): void {
    $middleware->preventRequestForgery(allowSameSite: true);
})

從 CSRF 保護中排除 URI

有時您可能希望將一組 URI 排除在 CSRF 保護之外。例如,如果您正在使用 Stripe 處理付款並使用其 webhook 系統,則需要將您的 Stripe webhook 處理路由排除在 CSRF 保護之外,因為 Stripe 不知道要傳送什麼 CSRF 令牌到您的路由。

通常,您應該將這類路由放置在 Laravel 在 routes/web.php 檔案中應用於所有路由的 web 中介層群組之外。但是,您也可以透過在應用程式的 bootstrap/app.php 檔案中向 preventRequestForgery 方法提供其 URI 來排除特定路由:

->withMiddleware(function (Middleware $middleware): void {
    $middleware->preventRequestForgery(except: [
        'stripe/*',
        'http://example.com/foo/bar',
        'http://example.com/foo/*',
    ]);
})

[!NOTE] 為了方便起見,當執行測試時,CSRF 中介層會自動為所有路由停用。

X-CSRF-TOKEN

除了檢查 CSRF 令牌作為 POST 參數外,PreventRequestForgery 中介層還會檢查 X-CSRF-TOKEN 請求標頭。例如,您可以將令牌儲存在 HTML meta 標籤中:

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

然後,您可以指示像 jQuery 這樣的函式庫自動將令牌新增到所有請求標頭中。這為使用傳統 JavaScript 技術的 AJAX 應用程式提供了簡單、方便的 CSRF 保護:

$.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    }
});

X-XSRF-TOKEN

Laravel 將當前 CSRF 令牌儲存在一個加密的 XSRF-TOKEN cookie 中,該 cookie 包含在框架生成的每個響應中。您可以使用 cookie 值來設定 X-XSRF-TOKEN 請求標頭。

此 cookie 主要作為開發者的便利功能傳送,因為某些 JavaScript 框架和函式庫(如 Angular 和 Axios)會自動將其值放在相同來源請求的 X-XSRF-TOKEN 標頭中。