§クロスサイトリクエストフォージェリに対する保護
クロスサイトリクエストフォージェリ(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-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機能は、暗黙のRequestHeader
(またはRequest
。これはRequestHeader
を拡張します)が暗黙のスコープで使用可能であることを前提としており、利用可能なものがないとコンパイルされません。以下に例を示します。
§アクションでの暗黙のリクエストの定義
CSRFトークンにアクセスする必要があるすべてのアクションの場合、リクエストは次のようにimplicit request =>
を使用して暗黙的に公開する必要があります
// this actions needs to access CSRF token
def someMethod: Action[AnyContent] = Action { implicit request =>
// access the token as you need
Ok
}
これは、CSRF.getToken
のようなヘルパーメソッドが、CSRFトークンを取得するためにリクエストを暗黙のパラメーターとして受信するためです。例:
def someAction: Action[AnyContent] = Action { implicit request =>
accessToken // request is passed implicitly to accessToken
Ok("success")
}
def accessToken(implicit request: Request[_]) = {
val token = CSRF.getToken // request is passed implicitly to CSRF.getToken
}
§メソッド間で暗黙のリクエストを渡す
CSRF機能が使用されるメソッドにコードを分割した場合は、アクションから暗黙のリクエストを渡すことができます
def action: Action[AnyContent] = Action { implicit request =>
anotherMethod("Some para value")
Ok
}
def anotherMethod(p: String)(implicit request: Request[_]) = {
// do something that needs access to the request
}
§テンプレートでの暗黙のリクエストの定義
HTMLテンプレートには、すでに存在しない場合は、テンプレートへの暗黙のRequestHeader
パラメーターが必要です。これは、CSRF.formField
ヘルパーで渡す必要があるためです(以下でさらに詳しく説明します)
@(...)(implicit request: RequestHeader)
通常、MessagesProvider
インスタンスを必要とするフォームヘルパーと組み合わせてCSRFを使用するため、MessagesAbstractController
またはMessagesRequestHeader
を提供する別のコントローラーを使用することをお勧めします
@(...)(implicit request: MessagesRequestHeader)
または、I18nSupport
を含むコントローラーを使用している場合は、メッセージを個別の暗黙のパラメーターとして渡すことができます
@(...)(implicit request: RequestHeader, messages: Messages)
§現在のトークンの取得
現在のCSRFトークンには、CSRF.getToken
メソッドを使用してアクセスできます。暗黙のRequestHeader
を受け取るため、スコープ内に1つあることを確認してください。
val token: Option[CSRF.Token] = CSRF.getToken
注記: CSRFフィルターがインストールされている場合、Playは使用されているクッキーがHttpOnly(JavaScriptからアクセスできない)である限り、トークンの生成を回避しようとします。厳密なボディを持つレスポンスを送信する場合、
CSRF.getToken
がすでに呼び出されていない限り、Playはレスポンスにトークンを追加するのをスキップします。これにより、CSRFトークンを必要としないレスポンスのパフォーマンスが大幅に向上します。クッキーがHttpOnlyに設定されていない場合、PlayはJavaScriptからアクセスしたいと想定し、トークンを生成します。
CSRFフィルターを使用していない場合は、CSRFAddToken
とCSRFCheck
のアクションラッパーをインジェクトして、特定のActionに対して強制的にトークンを追加したり、CSRFチェックを実行したりする必要もあります。そうしないと、トークンは利用できません。
import play.api.mvc._
import play.api.mvc.Results._
import play.filters.csrf._
import play.filters.csrf.CSRF.Token
class CSRFController(components: ControllerComponents, addToken: CSRFAddToken, checkToken: CSRFCheck)
extends AbstractController(components) {
def getToken =
addToken(Action { implicit request =>
val Token(name, value) = CSRF.getToken.get
Ok(s"$name=$value")
})
}
フォームにCSRFトークンを追加するのを助けるために、Playはいくつかのテンプレートヘルパーを提供します。最初のものは、アクションURLのクエリ文字列に追加します。
@import helper._
@form(CSRF(routes.ItemsController.save())) {
...
}
これは、このようなフォームをレンダリングする可能性があります。
<form method="POST" action="/items?csrfToken=1234567890abcdef">
...
</form>
クエリ文字列にトークンを含めたくない場合は、Playはフォームに隠しフィールドとしてCSRFトークンを追加するヘルパーも提供します。
@form(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つのアクションを提供します。
最初のアクションはCSRFCheck
アクションであり、チェックを実行します。セッション認証されたPOSTフォーム送信を受け入れるすべてのアクションに追加する必要があります。
import play.api.mvc._
import play.filters.csrf._
def save = checkToken {
Action { implicit req =>
// handle body
Ok
}
}
2番目のアクションは、CSRFAddToken
アクションであり、受信リクエストにCSRFトークンがまだ存在しない場合は生成します。フォームをレンダリングするすべてのアクションに追加する必要があります。
import play.api.mvc._
import play.filters.csrf._
def form = addToken {
Action { implicit req => Ok(views.html.itemsForm) }
}
これらのアクションを適用するより便利な方法は、Playのアクション合成と組み合わせて使用することです。
import play.api.mvc._
import play.filters.csrf._
class PostAction @Inject() (parser: BodyParsers.Default) extends ActionBuilderImpl(parser) {
override def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
// authentication code here
block(request)
}
override def composeAction[A](action: Action[A]) = checkToken(action)
}
class GetAction @Inject() (parser: BodyParsers.Default) extends ActionBuilderImpl(parser) {
override def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
// authentication code here
block(request)
}
override def composeAction[A](action: Action[A]) = addToken(action)
}
これにより、アクションを記述するために必要なボイラープレートコードを最小限に抑えることができます。
def save: Action[AnyContent] = postAction {
// handle body
Ok
}
def form: Action[AnyContent] = getAction { implicit req => Ok(views.html.itemsForm) }
§CSRF設定オプション
CSRF設定オプションの全範囲は、フィルターのreference.confにあります。いくつかの例を次に示します。
play.filters.csrf.token.name
- セッションとリクエストボディ/クエリ文字列の両方で使用するトークンの名前。デフォルトはcsrfToken
です。play.filters.csrf.cookie.name
- 設定されている場合、PlayはCSRFトークンをセッションではなく、指定された名前のクッキーに保存します。play.filters.csrf.cookie.secure
-play.filters.csrf.cookie.name
が設定されている場合、CSRFクッキーにセキュアフラグを設定するかどうか。デフォルトはplay.http.session.secure
と同じ値です。play.filters.csrf.body.bufferSize
- ボディからトークンを読み取るために、Playは最初にボディをバッファリングし、必要に応じて解析する必要があります。これにより、ボディのバッファリングに使用される最大バッファサイズを設定します。デフォルトは100kです。play.filters.csrf.token.sign
- Playが署名付きCSRFトークンを使用するかどうか。署名付きCSRFトークンは、トークン値がリクエストごとにランダム化され、BREACHスタイルの攻撃を防御することを保証します。
§コンパイル時依存性注入でのCSRFの使用
アプリケーションがコンパイル時依存性注入を使用している場合は、上記のすべての機能を使用できます。配線は、アプリケーションコンポーネントケーキにミックスインできるトレイトCSRFComponentsによって支援されます。コンパイル時依存性注入の詳細については、関連ドキュメントページを参照してください。
§CSRFのテスト
レンダリング時、テンプレートにCSRFトークンを追加する必要がある場合があります。import play.api.test.CSRFTokenHelper._
を使用すると、play.api.test.FakeRequest
にwithCSRFToken
メソッドが追加されます。
import play.api.test.CSRFTokenHelper._
import play.api.test.FakeRequest
import play.api.test.Helpers._
import play.api.test.WithApplication
class UserControllerSpec extends Specification {
"UserController GET" should {
"render the index page from the application" in new WithApplication() {
override def running() = {
val controller = app.injector.instanceOf[UserController]
val request = FakeRequest().withCSRFToken
val result = controller.userGet().apply(request)
status(result) must beEqualTo(OK)
contentType(result) must beSome("text/html")
}
}
}
}
次へ: カスタムバリデーション
このドキュメントにエラーが見つかりましたか?このページのソースコードはこちらにあります。 ドキュメントガイドラインを読んだ後、プルリクエストを貢献してください。質問や共有するアドバイスがありますか?コミュニティフォーラムにアクセスして、コミュニティとの会話を始めましょう。