§クロスサイトリクエストフォージェリ対策
クロスサイトリクエストフォージェリ (CSRF) は、攻撃者が被害者のブラウザに被害者のセッションを使ったリクエストを行わせるように仕向ける、セキュリティ上の脆弱性です。セッショントークンはすべてのリクエストにおいて送信されるので、攻撃者が被害者のブラウザに代わってリクエストを強制できる場合、攻撃者は被害者に代わってリクエストを行えるということになります。
CSRF の攻撃ベクトルがどのようなもので、何が攻撃ベクトルでないかについて習熟することをお勧めします。OWASP のこの情報 から始めるとよいでしょう。
端的に言えば、攻撃者は被害者のブラウザに次の種類のリクエストを行うよう強制することができます。
- あらゆる
GET
リクエスト - ボディタイプが
application/x-www-form-urlencoded
,multipart/form-data
そしてtext/plain
のPOST
リクエスト
攻撃者は次のことは行えません。
- この他の、
PUT
やDELETE
のようなリクエストを使うようブラウザに強制する - この他の、
application/json
のようなコンテントタイプを送信するようブラウザに強制する - サーバが既に設定したものとは異なる、新しいクッキーを送信するようブラウザに強制する
- ブラウザがリクエストに追加する通常のヘッダとは異なる、任意のヘッダを設定するようブラウザに強制する
GET
リクエストは状態を変えないことを意図しているので、このベストプラクティスに従っているアプリケーションに危険はありません。このため、CSRF 対策が必要なのは、上記で言及されたコンテントタイプを持つ POST
リクエストだけです。
§Play の CSRF 対策
Play はリクエストが CSRF リクエストでないことを検証する複数のメソッドを提供しています。その主要なメカニズムは CSRF トークンです。このトークンは、クエリ文字列、または投稿されたすべてのフォームのボディ部分に配置され、同様にユーザーのセッションにも配置されます。そして、Play はこの双方のトークンが存在し、一致することを検証します。
例えば AJAX を通じて行われるような、ブラウザ以外からのリクエストについて簡易に対策できるよう、Play は以下も提供しています。
X-Requested-With
ヘッダが存在する場合、Play はそのリクエストが安全であると見なします。X-Requested-With
は jQuery のようないくつかのポピュラーな Javascript ライブラリがリクエストに追加します。- 値が
nocheck
のCsrf-Token
ヘッダが存在する場合、または適切な CSRF トークンを含む場合、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.conf
の play.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 で見ることができます。以下はいくつかの例です。
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 型の攻撃を無効化することを保証します。
Next: カスタムバリデーション
このドキュメントの翻訳は Play チームによってメンテナンスされているものではありません。 間違いを見つけた場合、このページのソースコードを ここ で確認することができます。 ドキュメントガイドライン を読んで、お気軽にプルリクエストを送ってください。