CSRF 保護
簡介
CSRF (跨網站要求偽造,Cross-site Request Forgery) 是一種在通過登入使用者來進行未授權操作的惡意入侵方式。還好,Laravel 能讓你輕鬆保護網站免於遭受 CSRF 攻擊。
An Explanation of the Vulnerability
為避免讀者不熟悉 CSRF,我們來討論有關如何入侵該弱點的範例。假設專案中有個 /user/email
路由接受 POST
請求來修改登入使用者的 E-Mail 位址。顯然,這個路由預期有個 email
輸入欄位,其中包含了該使用者要使用的 E-Mail 位址。
若沒有 CSRF 保護,某個惡意網站可以建立一個 HTML 表單指向網站的 /user/email
路由,並送出假的使用者 E-Mail 位址:
1<form action="https://your-application.com/user/email" method="POST">3</form>45<script>6 document.forms[0].submit();7</script>
1<form action="https://your-application.com/user/email" method="POST">3</form>45<script>6 document.forms[0].submit();7</script>
若這個惡意網站在頁面載入後自動送出該表單,則惡意使用者只需要誘拐某個不經意的使用者瀏覽惡意網站,該使用者的 E-Mail 位址就會被修改。
為了防止此一弱點,我們需要在所有連入的 POST
, PUT
, PATCH
或 DELETE
請求上檢查某個私密 Session 值,該 Session 值必須是惡意網站無法存取的。
防止 CSRF 請求
Laravel 會自動為每個有效的使用者 Session 產生一個由網站管理的 CSRF「權杖 (Token)」。該權杖會用來認證正在登入的使用者是否真的是實際發起該請求的使用者。由於該權杖儲存於使用者 Session 內,且會在每次 Session 重新產生的時候更改,因此惡意網站無法存取該權杖。
可以通過請求的 Session 或是 csrf_token
輔助函式存取目前 Session 的 CSRF 權杖:
1use Illuminate\Http\Request;23Route::get('/token', function (Request $request) {4 $token = $request->session()->token();56 $token = csrf_token();78 // ...9});
1use Illuminate\Http\Request;23Route::get('/token', function (Request $request) {4 $token = $request->session()->token();56 $token = csrf_token();78 // ...9});
定義 "POST", "PUT", "PATCH", 或是 "DELETE" 的 HTML 表單時,應在表單內包含一個隱藏的 CSRF _token
欄位以讓 CSRF 保護 Middleware 認證該請求。為了方便起見,可以使用 @csrf
Blade 指示詞來產生這個隱藏的權杖輸入欄位:
1<form method="POST" action="/profile">2 @csrf34 <!-- Equivalent to... -->5 <input type="hidden" name="_token" value="{{ csrf_token() }}" />6</form>
1<form method="POST" action="/profile">2 @csrf34 <!-- Equivalent to... -->5 <input type="hidden" name="_token" value="{{ csrf_token() }}" />6</form>
The Illuminate\Foundation\Http\Middleware\ValidateCsrfToken
middleware, which is included in the web
middleware group by default, will automatically verify that the token in the request input matches the token stored in the session. When these two tokens match, we know that the authenticated user is the one initiating the request.
CSRF 權杖與 SPA
若正在建立使用 Laravel 作為 API 後端的 SPA,則可以考慮參考 Laravel Sanctum 說明文件瞭解有關使用 API 認證與保護 CSRF 弱點的資訊。
自 CSRF 保護內排除 URI
有時候,我們可能會想從 CSRF 保護內排除一些 URI。舉例來說,若正在使用 Stripe 來處理付款,並使用 Stripe 的 Webhook 系統,則需要將 Stripe Webhook 處理程式的路由從 CSRF 保護內排除,因為 Stripe 並不會知道要傳送什麼 CSRF 權杖給你的路由。
Typically, you should place these kinds of routes outside of the web
middleware group that Laravel applies to all routes in the routes/web.php
file. However, you may also exclude specific routes by providing their URIs to the validateCsrfTokens
method in your application's bootstrap/app.php
file:
1->withMiddleware(function (Middleware $middleware) {2 $middleware->validateCsrfTokens(except: [3 'stripe/*',4 'http://example.com/foo/bar',5 'http://example.com/foo/*',6 ]);7})
1->withMiddleware(function (Middleware $middleware) {2 $middleware->validateCsrfTokens(except: [3 'stripe/*',4 'http://example.com/foo/bar',5 'http://example.com/foo/*',6 ]);7})
為了方便起見,在執行測試時會自動禁用所有路由的 CSRF Middleware。
X-CSRF-TOKEN
In addition to checking for the CSRF token as a POST parameter, the Illuminate\Foundation\Http\Middleware\ValidateCsrfToken
middleware, which is included in the web
middleware group by default, will also check for the X-CSRF-TOKEN
request header. You could, for example, store the token in an HTML meta
tag:
1<meta name="csrf-token" content="{{ csrf_token() }}">
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});
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 值來設定 X-XSRF-TOKEN
請求標頭。
由於一些 JavaScript 框架如 Angular 與 Axios 會自動在同源請求時將該 Cookie 的值放在 X-XSRF-TOKEN
標頭內,該 Cookie 就是為了提供開發者方便而傳送的。
預設情況下,resources/js/bootstrap.js
檔案已包含了 Axios HTTP 函式庫,該函式庫會自動為你傳送 X-XSRF-TOKEN
標頭。