簡介 (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標頭。