monoの開発ブログ

Subscribe with livedoor Reader

Rubyで安全なWebアプリを作るためのメモ (2)

21 Feb 2012

前回の記事はてブのコメントrack-protectionについての言及がないというご指摘をいただきました。恥ずかしながらrack-protectionについては知らなかったので、少し調べてみました。

rack-protectionは、CSRFやXSSをはじめとした攻撃に対処するためのRack middlewareです。アプリケーションを起動する前にuse Rack::Protectionを呼び出して組み込むことですべての機能を簡単に利用できます。

require 'rack/protection'
use Rack::Protection
run App

ただ、rack-protectionが何をしているのか把握していないとはまりそうな部分もいくつかあるので、導入前に各機能の概要を理解しておいたほうがよいでしょう。

rack-protectionが実現している機能は以下の通りです。

  • CSRF対策
  • XSS対策
  • クリックジャッキング対策
  • ディレクトリトラバーサル対策
  • セッションハイジャッキング対策
  • IPスプーフィング対策

ここでは、各機能の概要を紹介します。機能ごとにそれぞれRack middlewareになっているので、use Rack::Protectionの代わりに個別にuseして利用することもできます。(option) と表記したmiddlewareはデフォルトでは組み込まれません。

CSRF対策

Rack::Protection::AuthenticityToken (option)

前回紹介したrack-csrfと同様に、POSTなどGET・HEAD・OPTIONS・TRACE以外のリクエスト時にsessionに格納しておいたtokenとリクエストのtokenを比較することでCSRFを防ぎます。

use Rack::Protection::AuthenticityToken

Rack::Protection::FormToken (option)

Rack::Protection::AuthenticityTokenのチェックに加えて、X-Requested-Withヘッダを確認してXMLHttpRequestによるアクセスだった場合のみ許可するようにします。これにより、攻撃者が用意したリンクを踏んでページを開かされても、リクエストが処理されることはありません。

use Rack::Protection::FormToken

Rack::Protection::JsonCsrf

同一ホストのリファラが付いていないJSON (application/json) へのリクエストに対して、エラーを返すことでJSONPを無効化します。これによってCSRFやJSONハイジャッキングを防ぐことができます。多くの場合、JSONPで外部にデータを提供する必要はないと思うのでこれで十分でしょう。しかし、JSONPで外部サイトに対してデータを提供する場合には、リクエストの検証を行うか、そもそも非公開のデータをJSONに含めないようにするなどの対策が必要です。

さらに、リファラをチェックする場合、ブラウザの設定でリファラを送信しないようにしているとエラーになってしまうという問題があります。これを防ぐために、デフォルトではリファラが空の場合は強制的にチェックを通過させるようになっています。一見丸く収まったかと思ってしまいますが、要するにリファラを送信していない人に対しては一切のCSRF対策をしていないということになるので、Rack::Protection::JsonCsrfを使うだけではすべての利用者に対して十分な対策を提供できているとは言えません。実際にはリファラを有効にしている人がほとんどでしょうから、ある程度は意味がありますし、別の方法でリクエストを検証している場合の補助的な対策としては有効でしょう。

use Rack::Protection::JsonCsrf

Rack::Protection::RemoteReferrer (option)

POSTなどGET・HEAD・OPTIONS・TRACE以外のリクエスト時にリファラをチェックし、同一ホストからでない場合はエラーを返します。こちらもRack::Protection::JsonCsrfと同様にリファラを無効にしている環境では意味がないという問題があるため、後述するRack::Protection::RemoteTokenを利用したほうがよいでしょう。

use Rack::Protection::RemoteReferrer

Rack::Protection::RemoteToken

Rack::Protection::AuthenticityTokenとRack::Protection::RemoteReferrerの組み合わせです。tokenとリファラの両方をチェックし、どちらか一方でも満たされない場合にはエラーになります。

use Rack::Protection::RemoteToken

XSS対策

Rack::Protection::EscapedParams

Rack::Request#paramsをエスケープします。ドキュメントには、Railsの場合は二重エスケープを避けるためにhtml_safeを呼ぶと記述されているのですが、私はRailsを使ったことがないのでよく分かりません。一応Sinatraで試してみましたが、案の定二重エスケープになってしまいました。

use Rack::Protection::EscapedParams

Rack::Protection::XssHeader

前回の記事ではnginxやApacheで行っていたX-XSS-Protectionヘッダの追加をRack middlewareとして実現したものです。どちらでやってもいいのですが、rack-protectionを使うなら任せてしまうのが手っ取り早いかもしれないですね。

use Rack::Protection::XssHeader

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

Rack::Protection::FrameOptions

これもRack::Protection::XssHeaderと同様にX-Frame-Optionsヘッダの追加を行います。

use Rack::Protection::FrameOptions

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

Rack::Protection::PathTraversal

PATH_INFOに含まれる/と.を展開してくれます。例えば、/foo/../bar は /bar に、 /foo/../../bar も /bar に変換されます。これにより、PATH_IFNOをそのままファイルパスに使ってファイルを開いたとしても、想定したディレクトリより上の階層を読み書きさせられることがなくなります。しかし、対象はPATH_INFOだけなのでQUERY_STRINGで渡されたパラメータに対しては全く意味がありません。そもそも外部からファイル名を直接指定するような実装をしないことで、根本的に問題を防いだほうがよいでしょう。

use Rack::Protection::PathTraversal

セッションハイジャッキング対策

Rack::Protection::SessionHijacking

初回アクセス時にUser-Agent・Accept-Encoding・Accept-Languageヘッダの内容をsession内に記憶しておき、一致しなくなった場合にセッションハイジャッキングが行われたと判断します。セッションハイジャッキングツールのFiresheepによる攻撃を防ぐのに有効なようです。しかし、ドキュメントにも書いてあるようにすべての攻撃を防げるものではないので、一般的なセッションハイジャッキング対策とあわせて利用すべきです。

use Rack::Protection::SessionHijacking

IPスプーフィング対策

Rack::Protection::IPSpoofing

Client-IPヘッダやX-Real-IPヘッダがX-Forwarded-Forヘッダと矛盾しないことをチェックします。これでIPスプーフィング攻撃を一部防ぐことができるようです。

use Rack::Protection::IPSpoofing