LaravelDocs(中文)

CSRF 保護 (CSRF Protection)

CSRF 保護 (CSRF Protection)

簡介 (Csrf Introduction)

跨站請求偽造 (Cross-site request forgery) 是一種惡意攻擊,會在使用者已通過驗證的情況下代為執行未經授權的操作。所幸,Laravel 讓你可以輕鬆防止 CSRF 攻擊。

漏洞說明 (Csrf Explanation)

若你對跨站請求偽造不熟悉,我們來看一個可能被濫用的例子。假設應用程式有個 /user/email 路由,可接受 POST 請求來修改已驗證使用者的 Email。通常該路由會期待 email 輸入欄位包含使用者想改用的 Email。

沒有 CSRF 保護時,惡意網站可以建立一個 HTML 表單,指向你應用程式的 /user/email 路由,並送出惡意使用者自己的 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>

若該惡意網站在載入頁面時自動送出表單,攻擊者只要誘使你應用程式的使用者造訪該網站,就能將帳號 Email 變成惡意使用者的 Email。

為了避免這種漏洞,我們需要在每個進來的 POST、PUT、PATCH 或 DELETE 請求上檢查一組惡意應用程式無法得知的秘密 Session 值。

防止 CSRF 請求 (Preventing Csrf Requests)

Laravel 會為每個由應用程式管理的作用中 使用者 Session 自動產生一組 CSRF「Token」。這組 Token 用來確認實際發出請求的人就是該已驗證使用者。由於 Token 儲存在使用者的 Session 中,且每次重新產生 Session 時都會更新,惡意應用程式無法取得它。

當前 Session 的 CSRF Token 可以透過 Request 的 Session 或 csrf_token 輔助函式存取:

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 保護 Middleware 能驗證請求。為了方便,你可以使用 @csrf Blade 指示詞來產生隱藏的 Token 輸入欄位:

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

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

預設包含在 web Middleware 群組內的 Illuminate\Foundation\Http\Middleware\ValidateCsrfToken Middleware 會自動驗證請求輸入中的 Token 是否與 Session 儲存的 Token 相符。當兩者一致時,我們便能確認是已驗證使用者正在發起請求。

CSRF Token 與 SPA (Csrf Tokens And Spas)

若你正在建立以 Laravel 作為 API 後端的 SPA,請參考 Laravel Sanctum 文件,瞭解如何與 API 進行驗證並防止 CSRF 漏洞。

從 CSRF 保護中排除 URI (Csrf Excluding Uris)

有時你可能想將某些 URI 排除在 CSRF 保護之外。例如,若你使用 Stripe 處理付款並採用他們的 Webhook 系統,就必須將 Stripe Webhook 處理路由排除,因為 Stripe 無法得知要傳給你路由的 CSRF Token。

一般來說,應該把這類路由放在 Laravel 自動套用於 routes/web.php 的 web Middleware 群組之外。不過,你也可以在應用程式的 bootstrap/app.php 中,透過 validateCsrfTokens 方法提供 URI 來排除特定路由:

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

[!NOTE] 為了方便,執行測試時會自動停用所有路由的 CSRF Middleware。

X-CSRF-TOKEN

除了檢查作為 POST 參數的 CSRF Token 外,預設包含在 web Middleware 群組內的 Illuminate\Foundation\Http\Middleware\ValidateCsrfToken Middleware 也會檢查 X-CSRF-TOKEN 請求標頭。你可以將 Token 存在 HTML meta 標籤中:

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

接著,你可以指示像 jQuery 這樣的函式庫,自動將 Token 加到所有請求標頭。這能為使用傳統 JavaScript 技術的 AJAX 應用程式提供簡單且便利的 CSRF 保護:

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

X-XSRF-TOKEN

Laravel 會將目前的 CSRF Token 儲存在加密的 XSRF-TOKEN Cookie 中,且每個由框架產生的回應都會包含此 Cookie。你可以使用 Cookie 的值來設定 X-XSRF-TOKEN 請求標頭。

此 Cookie 主要是為了方便開發者,因為某些 JavaScript 框架與函式庫(例如 Angular 與 Axios)會在同來源請求上自動將它的值放進 X-XSRF-TOKEN 標頭。

[!NOTE] 預設情況下,resources/js/bootstrap.js 會載入 Axios HTTP 函式庫,它會自動幫你送出 X-XSRF-TOKEN 標頭。