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。

為了避免這種漏洞,我們需要在每個進來的 POSTPUTPATCHDELETE 請求上檢查一組惡意應用程式無法得知的秘密 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.phpweb 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 標頭。