rack-protection とは

[rack-protection](http://rkh.github.io/rack-protection/) とは，Sinatra 関連のコンポーネント ([sinatra-contrib](https://github.com/rkh/sinatra-contrib) など) を多数作ってる [rkh](https://github.com/rkh) 氏のプロダクトの一つで，Rack に組み込むだけでいくつかの脆弱性に対する防御をしてくれる Rack middleware です。

いかがかなと思うコンポーネントもありますが，基本的にそういうのはデフォルトで無効化されてますし，`FrameOptions` や `XSSHeader` のように「とりあえず入れといても副作用ないし実効性がある」コンポーネントも多いので，とりあえずいれとくというスタンスでもよいのではないでしょうか。

# 使い方

ざっくりと省略。

とりあえず説明ページにあるように，`config.ru` に書く場合，

```ruby
# config.ru
require 'rack/protection'
use Rack::Protection
run MyApp
```

な感じで使うことになります。この `Rack::Protection` を `use` すると，バンドルされているコンポーネント (middleware) のうち穏当なもの (下記) が有効になります。

* `Rack::Protection::HttpOrigin`
* `Rack::Protection::RemoteToken`
* `Rack::Protection::JsonCsrf`
* `Rack::Protection::XSSHeader`
* `Rack::Protection::FrameOptions`
* `Rack::Protection::PathTraversal`
* `Rack::Protection::SessionHijacking`
* `Rack::Protection::IPSpoofing`

普通に使うならこれで十分だと思います。
(CSRF 対策は，リファラに整合性があるか，セッション内の CSRF トークンが一致するか，いずれかであれば OK とみなすルールとなります)

例えば，CSRF 対策に CSRF トークン埋め込みを使わずリファラチェックだけするのであれば，面倒ですが，

```ruby
use Rack::Protection::HttpOrigin
use Rack::Protection::RemoteReferrer
use Rack::Protection::JsonCsrf
use Rack::Protection::XSSHeader
use Rack::Protection::FrameOptions
use Rack::Protection::SessionHijacking
```

のように個別に middleware を積んでいくことになります。

## Padrino の場合

ちなみに Padrino の場合はデフォルトで `rack-protection` がバンドルされており，`config/apps.rb` の

```ruby
Padrino.configure_apps do
  set :protection, true
  set :protect_from_csrf, true
end
```

という部分で有効化されています。

* `protection` option が真の場合は (`Sinatra::Base#setup_protection` でやっている)
    * `Rack::Protection` が有効になる
    * `true` のかわりにハッシュによりオプションを設定できる
        * `:except` で除外するコンポーネントを指定できる
        * `:reaction` で，fault 時の動作を指定できる (デフォルトはセッション削除)
* `protect_from_csrf` option が真の場合は
    * `Rack::Protection::AuthenticityToken` が有効になる
    * `allow_disabled_csrf` option が偽の場合，CSRF リクエストが通らない
    * `allow_disabled_csrf` option が真の場合，CSRF リクエストは通る
        * Rack env の `protection.csrf.failed` というフィールドが真になり，アプリ側で検知できる



以下，各モジュールの簡単な説明。

# CSRF 対策

## `Rack::Protection::HttpOrigin`

`GET`, `HEAD` 等以外の場合で，`Origin` リクエストヘッダが存在する際，値が現在のリクエスト URL のものと適合するか，ホワイトリストに入っていれば OK。

## `Rack::Protection::AuthenticityToken` (デフォルト disabled)

`GET`, `HEAD` 等以外の場合に，セッションに格納されている CSRF トークンとリクエストの `X-CSRF-Token` ヘッダの値が同じであるか，フォームの `authenticity_token` 項目の値が同じであれば，OK。

* フォームの `authenticity_token` 項目に CSRF トークンを忍び込ませる場合
  * サーバ側でできるが，`rack-protection` suite はやってくれない。Padrino でデフォルトのフォームヘルパ・ビルダを使うと自動的に差し込んでくれる。
* `X-CSRF-Token` ヘッダをリクエストに忍び込ませる場合
  * サーバ側ではできないので，クライアントサイド (JavaScript とか) でなんとかする必要がある。あるいはネイティブアプリ等からのリクエストを想定しているのかな？

`FormToken` や `RemoteToken` のスーパークラスでもある。

## `Rack::Protection::FormToken` (デフォルト disabled)

`Rack::Protection::AuthenticityToken` のサブクラス。
リクエストの `X-Requested-With` ヘッダの値が `XMLHttpRequest` となってれば OK。
なっていない場合は `AuthencityToken` と同じチェックが入る。

どうしてこういう名前なのか解説しよう！
jQuery などメジャーな JavaScript ライブラリで Ajax 通信をおこなうと，`X-Requested-With` ヘッダに `XMLHttpRequest` という値をセットしてリクエストを送出する (ならわしになっている)。つまり，Ajax リクエストの場合には `AuthenticityToken` チェックをおこなわない (一方，ブラウザによる通常の `POST` リクエストの場合にはチェックをおこなう) ためのモジュールなのだ。

`AuthenticityToken` と `FormToken` をどちらも有効にするのは意味がない (`FormToken` の意味がなくなる―― Ajax の場合でも `AuthenticityToken` チェックが走る)。

(サーバ側で) クロスドメイン通信を許可していない場合 (たとえば `HttpOrigin` コンポーネントで遮断できる)，Ajax によるリクエストであることが確定していれば CSRF はおきていないとカジュアルには判断できるので，そのレベルの対策でいいやって場合にこっちを使う。
あるいは，既存のアプリケーションに CSRF 対策を追加する際，フォームに `authenticity_token` 項目を追加するのはフレームワークなどを利用しておまかせでできるけど，Ajax リクエストを発行している部分 (JavaScript コード) までは改造しきれねー，という場合に使う？

## `Rack::Protection::RemoteToken`

`Rack::Protection::AuthenticityToken` のサブクラス。
リファラのホストが現在のリクエスト先ホストと同一なら OK。
なっていない場合は `AuthencityToken` と同じチェックが入る。
(厳密には評価順は逆だけど)

`AuthenticityToken` と `RemoteToken` をどちらも有効にするのは意味がない (`RemoteToken` の意味がなくなる―― リファラに整合性がある場合でも `AuthenticityToken` チェックが走る)。

## `Rack::Protection::RemoteReferrer` (デフォルト disabled)

リファラのホストが現在のリクエスト先ホストと同一なら OK。

デフォルトの Protection suite を enable にしてなおかつ `RemoteReferrer` を enable する意味はあんまりない (デフォルトの `RemoteToken` でまかなえてしまうので)。CSRF トークンチェックを使わずにリファラチェックだけしたい場合に，こいつだけ読み込む。

## `Rack::Protection::JsonCsrf`

Ajax によるリクエストではなく，「レスポンスの内容が」 `application/json` の場合に，(たとえ `GET` 等であったとしても) リファラ等の整合性をチェックする。具体的には `Origin` ヘッダがセットされておらずリファラのホストが現在のホストと違っていれば NG。
(うーん，ここの条件が微妙というか `Origin` がセットされている場合にバイパスする理由がよくわかりません。誰か教えて)

JSON のコンテンツは JavaScript だからクロスドメインなサイトから `<script src="...">` で読み込めて，解釈できる。JSONP でない限りコールバックはされないだろうが，Object の prototype でセッタ等をいじっておけば，クロスドメインなサイト側からデータを取り出すことができる。一般には JSON ハイジャックと呼ばれる攻撃であり，これを防ぐために (とりあえず) リファラチェック等をする。

# XSS 対策

## `Rack::Protection::XSSHeader`

デフォルトオプションの状態で

* レスポンスが HTML の場合，レスポンスヘッダに `X-XSS-Protection: 1; mode=block` を追加する
* レスポンスヘッダに `X-Content-Type-Options: nosniff` を追加する

## `Rack::Protection::EscapedParams` (デフォルト disabled)

リクエストの `GET` パラメータ，`POST` パラメータの各値をエスケープする。 工ｴｴｪｪ(´д｀)ｪｪｴｴ工
デフォルトでは HTML escape と URI escape のみ。JavaScript エスケープもするなら escaper として [escape_utils](https://github.com/brianmario/escape_utils) を指定する必要がある。

# クリックジャッキング対策

クリックジャッキングとは ―― [IPAからクリックジャッキングに関するレポート出ました | 徳丸浩の日記](http://blog.tokumaru.org/2013/03/clickjacking-report-by-IPA.html)

## `Rack::Protection::FrameOptions`

レスポンスが HTML の場合に，レスポンスヘッダに `X-Frame-Options: SAMEORIGIN` (デフォルト設定の場合) を追加する。

# ディレクトリトラバーサル対策

ディレクトリトラバーサルとは―― [ディレクトリトラバーサル - Wikipedia](http://ja.wikipedia.org/wiki/%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E3%83%88%E3%83%A9%E3%83%90%E3%83%BC%E3%82%B5%E3%83%AB)

## `Rack::Protection::PathTraversal`

リクエストの `PATH_INFO` 環境変数にたいして，

* エスケープされているドットやスラッシュをアンエスケープし
* パスコンポーネントに `..` が含まれる場合は畳み込む

という操作をおこなう。

`PATH_INFO` に対する操作だけであることに注意が必要。

個人的にはそこまで必要性のない防御かなぁと思う (トラバーサルされないようにアプリ側で気をつけとけよという意味で)。

# セッションハイジャック対策

## `Rack::Protection::SessionHijacking`

(デフォルトで) `User-Agent`, `Accept-Encoding`, `Accept-Language` リクエストヘッダの内容を (暗号化して) セッションに保存する。セッションに保存されている場合，保存されている値と現在のヘッダの値を比較して違っていれば NG。

(そのへんのヘッダもいっしょに sniff されたら意味がないと思うけど……)

# IP スプーフィング対策

## `Rack::Protection::IPSpoofing`

リクエストヘッダに `X-Forwarded-For` ヘッダが含まれる場合，

* リクエストヘッダに `Client-IP` ヘッダがあったら，`X-Forwarded-For` ヘッダの内容と適合するか調べる (適合しなければ NG)
* リクエストヘッダに `X-Real-IP` ヘッダがあったら，`X-Forwarded-For` ヘッダの内容と適合するか調べる (適合しなければ NG)