§防範跨網站請求偽造
跨網站請求偽造 (CSRF) 是一種安全漏洞,攻擊者會誘騙受害者的瀏覽器使用受害者的工作階段發出請求。由於工作階段令牌會隨每個請求發送,如果攻擊者可以強迫受害者的瀏覽器代表他們發出請求,攻擊者就可以代表使用者發出請求。
建議您熟悉 CSRF、攻擊媒介是什麼,以及攻擊媒介不是什麼。我們建議從 OWASP 的此資訊 開始。
對於哪些要求是安全的,哪些容易受到 CSRF 要求的攻擊,沒有簡單的答案,原因在於對於外掛程式和規格的未來擴充功能允許什麼,沒有明確的規格。以往,瀏覽器外掛程式和擴充功能放寬了架構先前認為可以信任的規則,為許多應用程式引入了 CSRF 漏洞,而修復這些漏洞的責任落在架構上。基於此原因,Play 在其預設值中採取保守做法,但允許您確切設定何時執行檢查。預設情況下,當以下所有條件都為真時,Play 會要求執行 CSRF 檢查
- 要求方法不是
GET
、HEAD
或OPTIONS
。 - 要求有一個或多個
Cookie
或Authorization
標頭。 - CORS 篩選器未設定為信任要求的來源。
注意:如果您使用除 Cookie 或 HTTP 驗證以外的瀏覽器驗證,例如 NTLM 或基於用戶端憑證的驗證,則必須設定
play.filters.csrf.header.protectHeaders = null
,這將保護所有要求,或在protectHeaders
中包含驗證中使用的標頭。
§Play 的 CSRF 保護
Play 支援多種方法來驗證要求不是 CSRF 要求。主要機制是 CSRF 令牌。此令牌會放置在每個提交的表單的查詢字串或主體中,也會放置在使用者的工作階段中。然後,Play 會驗證兩個令牌是否存在且是否相符。
為了允許對非瀏覽器要求進行簡單的保護,Play 預設會檢查帶有 Cookie
或 Authorization
標頭的要求。您可以設定 play.filters.csrf.header.protectHeaders
來定義執行 CSRF 檢查時必須存在哪些標頭。如果您正在使用 AJAX 進行要求,您可以將 CSRF 令牌放置在 HTML 頁面中,然後使用 Csrf-Token
標頭將其新增到要求中。
或者,您可以設定 play.filters.csrf.header.bypassHeaders
來比對常見標頭:常見的設定會是
- 如果存在
X-Requested-With
標頭,Play 會將要求視為安全。X-Requested-With
會由許多熱門的 Javascript 函式庫(例如 jQuery)新增到要求中。 - 如果存在值為
nocheck
的Csrf-Token
標頭,或存在有效的 CSRF 令牌,Play 會將要求視為安全。
此設定會如下所示
play.filters.csrf.header.bypassHeaders {
X-Requested-With = "*"
Csrf-Token = "nocheck"
}
使用此設定選項時應小心,因為瀏覽器外掛程式在過去曾破壞過此類型的 CSRF 防禦。
§信任 CORS 要求
預設情況下,如果您在 CSRF 篩選器之前有一個 CORS 篩選器,CSRF 篩選器會允許來自受信任來源的 CORS 要求通過。若要停用此檢查,請設定設定選項 play.filters.csrf.bypassCorsTrustedOrigins = false
。
§套用全域 CSRF 篩選器
注意:從 Play 2.6.x 開始,CSRF 篩選器包含在 Play 的預設篩選器清單中,這些篩選器會自動套用到專案。請參閱 篩選器頁面 以取得更多資訊。
Play 提供了一個全域 CSRF 篩選器,可以套用到所有要求。這是將 CSRF 保護新增到應用程式的最簡單方法。若要手動新增篩選器,請將其新增到 application.conf
play.filters.enabled += "play.filters.csrf.CSRFFilter"
也可以在路由檔案中停用特定路由的 CSRF 篩選器。若要執行此動作,請在您的路由之前新增 nocsrf
修改器標籤
+ nocsrf
POST /api/new controllers.Api.newThing()
§取得目前的令牌
可以使用 CSRF.getToken
方法存取目前的 CSRF 令牌。它會取得一個 RequestHeader
Optional<CSRF.Token> token = CSRF.getToken(request);
注意:如果安裝了 CSRF 過濾器,只要使用的 cookie 是 HttpOnly(表示無法從 JavaScript 存取),Play 就會嘗試避免產生令牌。在傳送具有嚴格主體的回應時,除非已呼叫
CSRF.getToken
,否則 Play 會略過將令牌新增至回應。這會大幅提升不需要 CSRF 令牌的回應效能。如果未將 cookie 設定為 HttpOnly,Play 會假設您希望從 JavaScript 存取它,並無論如何都會產生它。
為了協助將 CSRF 令牌新增至表單,Play 提供了一些範本輔助程式。第一個會將它新增至動作 URL 的查詢字串
@import helper._
@form(CSRF(scalaguide.forms.csrf.routes.ItemsController.save())) {
...
}
這可能會呈現一個類似這樣的表單
<form method="POST" action="/items?csrfToken=1234567890abcdef">
...
</form>
如果不想在查詢字串中包含令牌,Play 也提供了一個輔助程式,可將 CSRF 令牌新增為表單中的隱藏欄位
@form(scalaguide.forms.csrf.routes.ItemsController.save()) {
@CSRF.formField
...
}
這可能會呈現一個類似這樣的表單
<form method="POST" action="/items">
<input type="hidden" name="csrfToken" value="1234567890abcdef"/>
...
</form>
§將 CSRF 令牌新增至階段
為了確保 CSRF 令牌可呈現於表單中並傳回給客戶端,如果傳入要求中尚未提供令牌,則全域過濾器會為所有接受 HTML 的 GET 要求產生新的令牌。
§逐動作套用 CSRF 過濾
有時全域 CSRF 過濾可能不適當,例如在應用程式可能想要允許一些跨來源表單張貼的情況。一些非基於階段的標準(例如 OpenID 2.0)需要使用跨網站表單張貼,或在伺服器對伺服器 RPC 通訊中使用表單提交。
在這些情況下,Play 提供了兩個可以與應用程式動作組合的動作。
第一個動作是 play.filters.csrf.RequireCSRFCheck
動作,它執行 CSRF 檢查。它應該新增到所有接受會話驗證的 POST 表單提交的動作
@RequireCSRFCheck
public Result save() {
// Handle body
return ok();
}
第二個動作是 play.filters.csrf.AddCSRFToken
動作,它會產生 CSRF 令牌,如果在傳入要求中尚未存在。它應該新增到所有呈現表單的動作
@AddCSRFToken
public Result get(Http.Request request) {
return ok(CSRF.getToken(request).map(CSRF.Token::value).orElse("no token"));
}
§CSRF 設定選項
CSRF 設定選項的完整範圍可以在 filters reference.conf 中找到。一些範例包括
play.filters.csrf.token.name
- 令牌的名稱,用於會話和要求主體/查詢字串。預設為csrfToken
。play.filters.csrf.cookie.name
- 如果設定,Play 會將 CSRF 令牌儲存在具有指定名稱的 cookie 中,而不是會話中。play.filters.csrf.cookie.secure
- 如果設定play.filters.csrf.cookie.name
,CSRF cookie 是否應該設定安全標記。預設為與play.http.session.secure
相同的值。play.filters.csrf.body.bufferSize
- 為了從主體中讀取令牌,Play 必須先緩衝主體並可能解析它。這設定用於緩衝主體的最大緩衝區大小。預設為 100k。play.filters.csrf.token.sign
- Play 是否應該使用已簽署的 CSRF 令牌。已簽署的 CSRF 令牌確保每個要求的令牌值是隨機的,因此可以擊敗 BREACH 類型的攻擊。
§測試 CSRF
在功能測試中,如果你要使用 CSRF 令牌來呈現 Twirl 範本,你需要一個可用的 CSRF 令牌。你可以透過在 play.mvc.Http.RequestBuilder
執行個體上呼叫 play.api.test.CSRFTokenHelper.addCSRFToken
來執行此操作
Http.RequestBuilder request = new Http.RequestBuilder().method(POST).uri("/xx/Kiwi");
request = CSRFTokenHelper.addCSRFToken(request);
下一步:使用表單範本輔助程式
在此文件集中發現錯誤?此頁面的原始程式碼可以在 這裡 找到。在閱讀 文件指南 後,請隨時提交拉取請求。有問題或建議要分享嗎?前往 我們的社群論壇 與社群展開對話。