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::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 トークン埋め込みを使わずリファラチェックだけするのであれば,面倒ですが,
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
という部分で有効化されています。
-
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
というフィールドが真になり,アプリ側で検知できる
- 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-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 を指定する必要がある。
クリックジャッキング対策
クリックジャッキングとは ―― 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)