rack-protection とは,Sinatra 関連のコンポーネント (sinatra-contrib など) を多数作ってる rkh 氏のプロダクトの一つで,Rack に組み込むだけでいくつかの脆弱性に対する防御をしてくれる Rack middleware です。
いかがかなと思うコンポーネントもありますが,基本的にそういうのはデフォルトで無効化されてますし,FrameOptions や XSSHeader のように「とりあえず入れといても副作用ないし実効性がある」コンポーネントも多いので,とりあえずいれとくというスタンスでもよいのではないでしょうか。
使い方
ざっくりと省略。
とりあえず説明ページにあるように,config.ru に書く場合,
# config.ru
require 'rack/protection'
use Rack::Protection
run MyApp
な感じで使うことになります。この Rack::Protection を use すると,バンドルされているコンポーネント (middleware) のうち穏当なもの (下記) が有効になります。
Rack::Protection::HttpOriginRack::Protection::RemoteTokenRack::Protection::JsonCsrfRack::Protection::XSSHeaderRack::Protection::FrameOptionsRack::Protection::PathTraversalRack::Protection::SessionHijackingRack::Protection::IPSpoofing
普通に使うならこれで十分だと思います。
(CSRF 対策は,リファラに整合性があるか,セッション内の CSRF トークンが一致するか,いずれかであれば OK とみなすルールとなります)
例えば,CSRF 対策に CSRF トークン埋め込みを使わずリファラチェックだけするのであれば,面倒ですが,
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 の
Padrino.configure_apps do
set :protection, true
set :protect_from_csrf, true
end
という部分で有効化されています。
-
protectionoption が真の場合は (Sinatra::Base#setup_protectionでやっている)-
Rack::Protectionが有効になる -
trueのかわりにハッシュによりオプションを設定できる-
:exceptで除外するコンポーネントを指定できる -
:reactionで,fault 時の動作を指定できる (デフォルトはセッション削除)
-
-
-
protect_from_csrfoption が真の場合は-
Rack::Protection::AuthenticityTokenが有効になる -
allow_disabled_csrfoption が偽の場合,CSRF リクエストが通らない -
allow_disabled_csrfoption が真の場合,CSRF リクエストは通る- Rack env の
protection.csrf.failedというフィールドが真になり,アプリ側で検知できる
- Rack env の
-
以下,各モジュールの簡単な説明。
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-protectionsuite はやってくれない。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 を指定する必要がある。
クリックジャッキング対策
クリックジャッキングとは ―― IPAからクリックジャッキングに関するレポート出ました | 徳丸浩の日記
Rack::Protection::FrameOptions
レスポンスが HTML の場合に,レスポンスヘッダに X-Frame-Options: SAMEORIGIN (デフォルト設定の場合) を追加する。
ディレクトリトラバーサル対策
ディレクトリトラバーサルとは―― ディレクトリトラバーサル - Wikipedia
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)