Documentation

§クロスサイトリクエストフォージェリ対策

クロスサイトリクエストフォージェリ (CSRF) は、攻撃者が被害者のブラウザに被害者のセッションを使ったリクエストを行わせるように仕向ける、セキュリティ上の脆弱性です。セッショントークンはすべてのリクエストにおいて送信されるので、攻撃者が被害者のブラウザに代わってリクエストを強制できる場合、攻撃者は被害者に代わってリクエストを行えるということになります。

CSRF の攻撃ベクトルがどのようなもので、何が攻撃ベクトルでないかについて習熟することをお勧めします。OWASP のこの情報 から始めるとよいでしょう。

端的に言えば、攻撃者は被害者のブラウザに次の種類のリクエストを行うよう強制することができます。

攻撃者は次のことは行えません。

GET リクエストは状態を変えないことを意図しているので、このベストプラクティスに従っているアプリケーションに危険はありません。このため、CSRF 対策が必要なのは、上記で言及されたコンテントタイプを持つ POST リクエストだけです。

§Play の CSRF 対策

Play はリクエストが CSRF リクエストでないことを検証する複数のメソッドを提供しています。その主要なメカニズムは CSRF トークンです。このトークンは、クエリ文字列、または投稿されたすべてのフォームのボディ部分に配置され、同様にユーザーのセッションにも配置されます。そして、Play はこの双方のトークンが存在し、一致することを検証します。

例えば AJAX を通じて行われるような、ブラウザ以外からのリクエストについて簡易に対策できるよう、Play は以下も提供しています。

§グローバル CSRF フィルタの適用

Play はすべてのリクエストに適用できるグローバル CSRF フィルタを提供しています。これがアプリケーションに CSRF 対策を追加するもっとも簡単な方法です。このグローバルフィルタを利用できるようにするには、Play フィルタヘルパーの依存性をプロジェクトの build.sbt に追加します。

libraryDependencies += filters

HTTP フィルタ の説明に従って、それらを Filters クラスに追加してください。

import play.api.http.HttpFilters
import play.filters.csrf.CSRFFilter
import javax.inject.Inject

class Filters @Inject() (csrfFilter: CSRFFilter) extends HttpFilters {
  def filters = Seq(csrfFilter)
}

Filters クラスは、ルートパッケージに含まれていても別の名前を持っていても別のパッケージに入っていても、application.confplay.http.filters を使って設定する必要があります。

play.http.filters = "filters.MyFilters"

§現在のトークンを取得する

現在の CSRF トークンには getToken メソッドを使ってアクセスすることができます。このメソッドは implicit な RequestHeader を取るので、これがスコープに存在することを確認してください。

import play.filters.csrf.CSRF

val token = CSRF.getToken(request)

フォームへの CSRF トークンの追加を支援するために、Play はいくつかのテンプレートヘルパを提供しています。最初のひとつはトークンをアクション URL のクエリ文字列に追加します。

@import helper._

@form(CSRF(routes.ItemsController.save())) {
    ...
}

これはフォームを以下のようにレンダリングするでしょう。

<form method="POST" action="/items?csrfToken=1234567890abcdef">
   ...
</form>

クエリ文字列にトークンを持つことが望しくない場合もあるので、Play は CSRF トークンをフォーム内の hidden フィールドとして追加するヘルパも提供しています。

@form(routes.ItemsController.save()) {
    @CSRF.formField
    ...
}

これはフォームを以下のようにレンダリングするでしょう。

<form method="POST" action="/items">
   <input type="hidden" name="csrfToken" value="1234567890abcdef"/>
   ...
</form>

これらのフォームヘルパーメソッドは、すべてスコープ上で利用できる implicit なトークンまたはリクエストを必要とします。これらは通常、implicit な RequestHeader パラメータがまだテンプレートに存在しない場合、テンプレートに追加することで提供されます。

§CSRF トークンをセッションに追加する

CSRF トークンがフォーム内にレンダリングされ、そしてクライアントに送り返されることを保証するために、グローバルフィルタは HTML を受け取るすべての GET リクエストにおいて、そのリクエストで既にトークンが利用可能でない場合は、新しいトークンを生成します。

§アクションごとに CSRF フィルタリングを適用する

例えば、アプリケーションがいくつかのサイトをまたがったフォーム送信を許可するようにしたい場合など、グローバル CSRF フィルタが適切でない場合もあるかもしれません。Open ID 2.0 のようなセッションを前提としない標準は、サイトをまたがったフォームの送信、または複数サーバ間の RPC コミュニケーションによるフォーム送信を要求します。

このような状況のために、Play はアプリケーションのアクションに合成できる二つのアクションを提供しています。

最初のひとつは、検査を行う CSRFCheck アクションです。これは、セッションで認証される POST フォームの投稿を受け取るすべてのアクションに追加する必要があります。

import play.api.mvc._
import play.filters.csrf._

def save = CSRFCheck {
  Action { req =>
    // handle body
    Ok
  }
}

二つ目は、入力されたリクエストに既に存在しなければ CSRF トークンを生成する CSRFAddToken アクションです。これは、フォームをレンダリングするすべてのアクションに追加する必要があります。

import play.api.mvc._
import play.filters.csrf._

def form = CSRFAddToken {
  Action { implicit req =>
    Ok(views.html.itemsForm())
  }
}

これらのアクションをもっと便利に適用する方法は、これらのアクションを Play の アクションの構成 と組み合わせて使う方法です。

import play.api.mvc._
import play.filters.csrf._

object PostAction extends ActionBuilder[Request] {
  def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
    // authentication code here
    block(request)
  }
  override def composeAction[A](action: Action[A]) = CSRFCheck(action)
}

object GetAction extends ActionBuilder[Request] {
  def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
    // authentication code here
    block(request)
  }
  override def composeAction[A](action: Action[A]) = CSRFAddToken(action)
}

これでアクションを書くために必要なボイラープレートコードを最小化することができます。

def save = PostAction {
  // handle body
  Ok
}

def form = GetAction { implicit req =>
  Ok(views.html.itemsForm())
}

§CSRF 設定オプション

CSRF 設定オプションのすべては、フィルタ reference.conf で見ることができます。以下はいくつかの例です。

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


このドキュメントの翻訳は Play チームによってメンテナンスされているものではありません。 間違いを見つけた場合、このページのソースコードを ここ で確認することができます。 ドキュメントガイドライン を読んで、お気軽にプルリクエストを送ってください。