§クロスサイトリクエストフォージェリに対する保護
クロスサイトリクエストフォージェリ(CSRF)は、攻撃者が被害者のブラウザをだまして、被害者のセッションを使用してリクエストを送信させるセキュリティ攻撃です。セッション トークンはすべてのリクエストで送信されるため、攻撃者が被害者のブラウザを強制的に自分の代わりにリクエストを送信させることができる場合、攻撃者はユーザーの代わりにリクエストを送信できます。
CSRF、攻撃ベクトルの種類、および攻撃ベクトルではないものをよく理解しておくことをお勧めします。まずは、OWASPからのこの情報から始めることをお勧めします。
どのリクエストが安全で、どのリクエストがCSRFリクエストに対して脆弱であるかについては、簡単な答えはありません。その理由は、プラグインや仕様の将来の拡張から何が許可されるかについての明確な仕様がないためです。歴史的に、ブラウザのプラグインと拡張機能は、フレームワークが以前は信頼できると考えていたルールを緩和し、多くのアプリケーションにCSRFの脆弱性を導入しており、それらを修正する責任はフレームワークにありました。このため、Playはデフォルトで控えめなアプローチを取りますが、チェックを実行するタイミングを正確に構成できます。デフォルトでは、次のすべてが当てはまる場合、PlayはCSRFチェックを要求します。
- リクエストメソッドが
GET
、HEAD
、またはOPTIONS
でない。 - リクエストに1つ以上の
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
は、jQueryなどの多くの一般的なJavaScriptライブラリによってリクエストに追加されます。- 値が
nocheck
であるか、有効なCSRFトークンを持つCsrf-Token
ヘッダーが存在する場合、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"
routesファイルで特定のルートのCSRFフィルターを無効にすることもできます。これを行うには、ルートの前にnocsrf
修飾子タグを追加します。
+ nocsrf
POST /api/new controllers.Api.newThing()
§現在のトークンの取得
現在のCSRFトークンは、CSRF.getToken
メソッドを使用してアクセスできます。これは、RequestHeader
を受け取ります。
Optional<CSRF.Token> token = CSRF.getToken(request);
注:CSRFフィルターがインストールされている場合、Playは使用されているCookieがHttpOnly(つまり、JavaScriptからアクセスできない)である限り、トークンの生成を回避しようとします。厳密なボディを持つレスポンスを送信する場合、Playは
CSRF.getToken
がすでに呼び出されていない限り、レスポンスへのトークンの追加をスキップします。これにより、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はアプリケーションのアクションと組み合わせて構成できる2つのアクションを提供します。
最初のアクションは、CSRFチェックを実行するplay.filters.csrf.RequireCSRFCheck
アクションです。セッション認証されたPOSTフォーム送信を受け入れるすべてのアクションに追加する必要があります。
@RequireCSRFCheck
public Result save() {
// Handle body
return ok();
}
2番目のアクションは、受信リクエストにまだ存在しない場合にCSRFトークンを生成するplay.filters.csrf.AddCSRFToken
アクションです。フォームをレンダリングするすべてのアクションに追加する必要があります。
@AddCSRFToken
public Result get(Http.Request request) {
return ok(CSRF.getToken(request).map(CSRF.Token::value).orElse("no token"));
}
§CSRF構成オプション
CSRF構成オプションの完全な範囲は、フィルターの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にsecureフラグを設定する必要があるかどうか。デフォルトは、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);
このドキュメントにエラーを見つけましたか?このページのソースコードはこちらにあります。 ドキュメントガイドラインをお読みになった後、プルリクエストをお気軽にお送りください。質問や共有したいアドバイスはありますか? コミュニティフォーラムでコミュニティとの会話を始めましょう。