LoginSignup
69

More than 5 years have passed since last update.

rack-protection とは

Last updated at Posted at 2013-06-19

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

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

使い方

ざっくりと省略。

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

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

な感じで使うことになります。この Rack::Protectionuse すると,バンドルされているコンポーネント (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 というフィールドが真になり,アプリ側で検知できる

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

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 とか) でなんとかする必要がある。あるいはネイティブアプリ等からのリクエストを想定しているのかな?

FormTokenRemoteToken のスーパークラスでもある。

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

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

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

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

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

Rack::Protection::RemoteToken

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

AuthenticityTokenRemoteToken をどちらも有効にするのは意味がない (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)

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
69