#356 Dangers of Session Hijacking
- Download:
- source codeProject Files in Zip (103 KB)
- mp4Full Size H.264 Video (20.8 MB)
- m4vSmaller H.264 Video (8.45 MB)
- webmFull Size VP8 Video (8.46 MB)
- ogvFull Size Theora Video (18.7 MB)
ToDoアプリケーションがあり、ユーザは登録あるいはログインができます。パスワードなど取り扱いに注意が必要なユーザ情報を受け付ける場合は、常にセキュアな接続で送信されることが重要なので、平文ではなく暗号化します。つまり、ログインやユーザ登録フォームでは常にHTTPSを使用するべきです。
一度ログインすると、その後はもう秘密情報をやり取りしないのでHTTP接続に戻すということがよく行なわれます。しかしこの場合、アプリケーションがセッションハイジャックの被害にあう可能性があります。別名sidejackingと呼ばれるこの手法は、2年ほど前にFirefoxの機能拡張のFiresheepによって知られるようになりました。これを利用すると、公共のWiFiスポットでローカルネットワークのトラフィックをモニタリングして、他のユーザのセキュアでない接続で行なわれるセッションをハイジャックすることが可能になります。
Firesheepを使わずにRailsアプリケーションからセッションがハイジャックされる様子を実際に見てみましょう。今回はtcpdump
コマンドを使用します。これはMac OS Xに含まれているコマンドですが、他のOSにも似たようなコマンドがあります。このコマンドを使ってネットワークインターフェース(通常はen0
かen2
)のトラフィックを調べることができますが、ここではモニタしようとするRailsアプリケーションがlocalhostで実行されているのでlo0
を使います。また-A
フラグを使用して、出力をASCIIテキストとして出力します。
$ sudo tcpdump -i lo0 -A
次にRailsアプリケーションで、ログイン操作中にトラフィックを発生させるために、何度かページをリロードします。tcpdump
の出力の中に、先ほど行なったリクエストがあり、その中にセッションクッキーが含まれているはずです。
Host: todo.dev User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/534.57.2 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2 Accept: text/css,*/*;q=0.1 If-Modified-Since: Fri, 15 Jun 2012 20:08:50 GMT Referer: http://todo.dev/ If-None-Match: "12ab47955f6d80ec910edb372bffd425" Accept-Language: en-gb Accept-Encoding: gzip, deflate Cookie: _todo_session=BAh7CEkiD3Nlc3Npb25faWQGOgZFRkkiJWY4MDAwM2ZjNDRiOTZhODY4MzRiYjQ3ZTA0NmM4ZTllBjsAVEkiEF9jc3JmX3Rva2VuBjsARkkiMW5qQmM2NjRYeFVpUlFDam9NL2NhM01zYkhCeWpzYVJibzBXNXNNZkZCbEE9BjsARkkiDHVzZXJfaWQGOwBGaQg%3D--8532435178d7a4e85ddaf4bd5f7170d1b8a9adbe Connection: keep-alive
セッションクッキーを取得できたので、CTRL+Cでtcpdump
を終了します。このクッキー情報を利用して、パスワードはわからないまま、curl
を用いてそのユーザとしてサイトにアクセスしてみます。curl
でトップページにアクセスすると、ログイン前の画面が表示されます。
$ curl http://todo.dev/ ... <p><strong>Currently not logged in.</strong></p> ...
しかし、セッションクッキーを渡すと、セッションクッキーを取得した元ユーザとしてログインした画面が表示されます。
$ curl http://todo.dev/ -H 'Cookie: _todo_session=BAh7CEkiD3Nlc3Npb25faWQGOgZFRkkiJWY4MDAwM2ZjNDRiOTZhODY4MzRiYjQ3ZTA0NmM4ZTllBjsAVEkiEF9jc3JmX3Rva2VuBjsARkkiMW5qQmM2NjRYeFVpUlFDam9NL2NhM01zYkhCeWpzYVJibzBXNXNNZkZCbEE9BjsARkkiDHVzZXJfaWQGOwBGaQg%3D--8532435178d7a4e85ddaf4bd5f7170d1b8a9adbe' ... <p id="logged_in"> Logged in as <strong>eifion</strong>. <a href="/logout">Log Out</a> </p> ...
Railsアプリケーションを保護する
これが意味するのは、接続しようとしているサイトがHTTPSですべてのリクエスト(クッキーを含む)を暗号化していない限り、同じネットワーク上の誰でもが、任意のユーザのセッションを簡単にハイジャックできるということであり、心配な事態です。Railsアプリケーションを強制的に暗号化させるにはどうすればいいでしょうか? アプリケーションの本番環境の設定ファイルを見ると、force_ssl
という設定項目があり、コメントアウトされています。これを非コメント化すると、RailsアプリケーションのすべてのリクエストでHTTPSを使用することが強制されます。この効果を確認するために、開発環境でこの設定を有効にしてみます。
config.force_ssl = true
アプリケーションの再起動後にアプリケーションの適当なページにHTTPでアクセスすると、対応するHTTPSページにリダイレクトされます。開発環境のHTTPSサイトをテストするのは簡単ではない場合があります。今回の例ではNginxとPowの組み合わせで動作させているのですが、その方法の詳細を今週のproエピソードで紹介しています。
force_ssl
設定は、アプリケーション中のすべてのページをセキュアにする前提になっています。もしサイトの一部のみでSSLを有効化したい場合は、この設定は使わずにコントローラレベルで制限をかけるべきですが、これはクッキーが平文のままで送信されるのでセッションハイジャックに対して脆弱になります。解決方法のひとつは、ユーザをログインさせるコントローラを編集して、クッキーを一つではなく2つ発行し、そのうちの一つをセキュアにします。
今回のアプリケーションのログイン動作はSessionsController
にあり、create
アクションでユーザがログインします。このときにid
がセッションに保存されますが、同時にクッキーにも保存するようにします。クッキーには署名を入れてユーザが修正できないようにし、名前をsecure_user_id
とします。またこれをセキュアな接続のみで渡されるように設定します。
def create user = User.find_by_name(params[:name]) if user && user.authenticate(params[:password]) session[:user_id] = user.id cookies.signed[:secure_user_id] = {secure: true, value: "secure#{user.id}"} redirect_to(session.delete(:return_to) || root_url) else flash.now.alert = "Email or password is invalid" render "new" end end
ユーザがログアウトするとdestroy
アクションが呼び出されますが、これを修正してクッキーが削除されるようにします。
def destroy session[:user_id] = nil cookies.delete[:secure_user_id] redirect_to login_url end
現在のユーザを取得するとこの追加のクッキーをチェックできます。これをApplicationController
のcurrent_user
メソッドで行ないます。このメソッドを修正して、現在のリクエストがセキュアな接続ではないか、あるいはそうであればsecure_user_id
クッキーが正しい値を持つかをチェックします。
def current_user if !request.ssl? || cookies.signed[:secure_user_id] == "secure#{session[:user_id]}" @current_user ||= User.find(session[:user_id]) if session[:user_id] end end
これによって、セッションがハイジャックされても、そのユーザになりすましてセキュアなページにアクセスすることはできません。
アプリケーションにログインすると2つのクッキーが生成されるようになりました。一つはセキュアな接続用、もう一つは通常の接続用です。ユーザ側から見ると、アプリケーションがHTTPとHTTPSを切り替えている間もずっとログインしたままですが、誰かがセッションをハイジャックしようとしても取得できるのはセキュアでないクッキーだけです。これを使うことで、セキュアでないページに対して、そのユーザになりすまして接続することはできますが、HTTPSを介してのみアクセスできるページを取得しようとすると、セキュアクッキーが一致しないためページにはログインしていないというメッセージが表示されます。
この方法を使うと、ハイジャック目的で通常のHTTPリクエストで別のユーザになりすますことはできますが、HTTPSリクエストではアクセスが許可されません。つまり、セッションハイジャックによってアクセスされたくないページはSSLに設定することを徹底するべきです。