ドキュメント

§クロスサイトリクエストフォージェリに対する保護

クロスサイトリクエストフォージェリ(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はデフォルトでCookieまたはAuthorizationヘッダーを持つリクエストをチェックします。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機能は、暗黙の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フィルターを使用していない場合は、CSRFAddTokenCSRFCheckのアクションラッパーをインジェクトして、特定の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にあります。いくつかの例を次に示します。

§コンパイル時依存性注入でのCSRFの使用

アプリケーションがコンパイル時依存性注入を使用している場合は、上記のすべての機能を使用できます。配線は、アプリケーションコンポーネントケーキにミックスインできるトレイトCSRFComponentsによって支援されます。コンパイル時依存性注入の詳細については、関連ドキュメントページを参照してください。

§CSRFのテスト

レンダリング時、テンプレートにCSRFトークンを追加する必要がある場合があります。import play.api.test.CSRFTokenHelper._を使用すると、play.api.test.FakeRequestwithCSRFTokenメソッドが追加されます。

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")
      }
    }
  }
}

次へ: カスタムバリデーション


このドキュメントにエラーが見つかりましたか?このページのソースコードはこちらにあります。 ドキュメントガイドラインを読んだ後、プルリクエストを貢献してください。質問や共有するアドバイスがありますか?コミュニティフォーラムにアクセスして、コミュニティとの会話を始めましょう。