文件

§防範跨網站請求偽造

跨網站請求偽造 (CSRF) 是一種安全漏洞,攻擊者會誘騙受害者的瀏覽器使用受害者的工作階段發出請求。由於工作階段令牌會隨每個請求發送,如果攻擊者可以強迫受害者的瀏覽器代表他們發出請求,攻擊者就可以代表使用者發出請求。

建議您熟悉 CSRF、攻擊媒介是什麼,以及攻擊媒介不是什麼。我們建議從 OWASP 的此資訊 開始。

對於哪些要求是安全的,哪些容易受到 CSRF 要求的攻擊,沒有簡單的答案,原因在於對於外掛程式和規格的未來擴充功能允許什麼,沒有明確的規格。以往,瀏覽器外掛程式和擴充功能放寬了架構先前認為可以信任的規則,為許多應用程式引入了 CSRF 漏洞,而修復這些漏洞的責任落在架構上。基於此原因,Play 在其預設值中採取保守做法,但允許您確切設定何時執行檢查。預設情況下,當以下所有條件都為真時,Play 會要求執行 CSRF 檢查

注意:如果您使用除 Cookie 或 HTTP 驗證以外的瀏覽器驗證,例如 NTLM 或基於用戶端憑證的驗證,則必須設定 play.filters.csrf.header.protectHeaders = null,這將保護所有要求,或在 protectHeaders 中包含驗證中使用的標頭。

§Play 的 CSRF 保護

Play 支援多種方法來驗證要求不是 CSRF 要求。主要機制是 CSRF 令牌。此令牌會放置在每個提交的表單的查詢字串或主體中,也會放置在使用者的工作階段中。然後,Play 會驗證兩個令牌是否存在且是否相符。

為了允許對非瀏覽器要求進行簡單的保護,Play 預設會檢查帶有 CookieAuthorization 標頭的要求。您可以設定 play.filters.csrf.header.protectHeaders 來定義執行 CSRF 檢查時必須存在哪些標頭。如果您正在使用 AJAX 進行要求,您可以將 CSRF 令牌放置在 HTML 頁面中,然後使用 Csrf-Token 標頭將其新增到要求中。

或者,您可以設定 play.filters.csrf.header.bypassHeaders 來比對常見標頭:常見的設定會是

此設定會如下所示

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 中找到。一些範例包括

§測試 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);

下一步:使用表單範本輔助程式


在此文件集中發現錯誤?此頁面的原始程式碼可以在 這裡 找到。在閱讀 文件指南 後,請隨時提交拉取請求。有問題或建議要分享嗎?前往 我們的社群論壇 與社群展開對話。