跳轉至內容

CSRF 保護

簡介

跨站請求偽造(CSRF)是一種惡意攻擊,它允許攻擊者以已認證使用者的名義執行未經授權的命令。值得慶幸的是,Laravel 可以輕鬆保護您的應用程式免受 跨站請求偽造 (CSRF) 的攻擊。

漏洞解釋

如果您不熟悉跨站請求偽造,我們來探討一個關於該漏洞如何被利用的示例。假設您的應用程式有一個 /user/email 路由,該路由接受 POST 請求以更改已認證使用者的電子郵件地址。最有可能的情況是,此路由期望 email 輸入欄位包含使用者希望開始使用的電子郵件地址。

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

1<form action="https://your-application.com/user/email" method="POST">
2 <input type="email" value="[email protected]">
3</form>
4 
5<script>
6 document.forms[0].submit();
7</script>

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

為了防止此漏洞,我們需要檢查每個傳入的 POSTPUTPATCHDELETE 請求,確認其是否包含惡意應用程式無法獲取的加密會話值。

防止 CSRF 請求

Illuminate\Foundation\Http\Middleware\PreventRequestForgery 中介軟體(預設包含在 web 中介軟體組中)透過雙層方法保護您的應用程式免受跨站請求偽造的影響。

首先,該中介軟體會檢查瀏覽器的 Sec-Fetch-Site 請求頭。現代瀏覽器會在每次請求時自動設定此標頭,以表明請求是源自相同源、相同站點還是跨站來源。如果標頭顯示請求來自相同源,則無需任何令牌驗證即可直接允許該請求。

如果源驗證未透過(例如,因為請求來自不支援傳送 Sec-Fetch-Site 標頭的舊版瀏覽器,或者連線不安全),中介軟體將回退到傳統的 CSRF 令牌驗證。

Laravel 會自動為應用程式管理的每個活躍的 使用者會話 生成一個 CSRF “令牌”。該令牌用於驗證發出請求的使用者是否正是已認證的本人。由於此令牌儲存在使用者的會話中,並且在每次重置會話時都會更改,因此惡意應用程式無法獲取它。

當前會話的 CSRF 令牌可以透過請求的會話或 csrf_token 輔助函式獲取。

1use Illuminate\Http\Request;
2 
3Route::get('/token', function (Request $request) {
4 $token = $request->session()->token();
5 
6 $token = csrf_token();
7 
8 // ...
9});

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

1<form method="POST" action="/profile">
2 @csrf
3 
4 <!-- Equivalent to... -->
5 <input type="hidden" name="_token" value="{{ csrf_token() }}" />
6</form>

CSRF 令牌與 SPA

如果您正在構建一個使用 Laravel 作為 API 後端的 SPA(單頁應用),請參閱 Laravel Sanctum 文件,瞭解有關 API 身份驗證及防止 CSRF 漏洞的資訊。

源驗證

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

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

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

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

Sec-Fetch-Site 標頭僅由瀏覽器透過安全 (HTTPS) 連線傳送。如果您的應用程式不是透過 HTTPS 提供服務,則無法使用源驗證,中介軟體將回退到 CSRF 令牌驗證。

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

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

從 CSRF 保護中排除 URI

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

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

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

為了方便起見,當 執行測試 時,CSRF 中介軟體會自動為所有路由停用。

X-CSRF-TOKEN

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

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

然後,您可以指示 jQuery 之類的庫自動將該令牌新增到所有請求標頭中。這為使用舊版 JavaScript 技術的 AJAX 應用程式提供了簡單、便捷的 CSRF 保護。

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

X-XSRF-TOKEN

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

傳送此 cookie 主要是為了開發人員的便利,因為一些 JavaScript 框架和庫(如 Angular 和 Axios)會自動將其值放置在相同源請求的 X-XSRF-TOKEN 標頭中。

預設情況下,resources/js/bootstrap.js 檔案中包含了 Axios HTTP 庫,它會自動為您傳送 X-XSRF-TOKEN 標頭。