Seasar2 の dbsession について
仕事で Seaser2 の dbsession を使っている。
セッションの中身を DB に保存することができるため、アプリケーションのクラスタリングが容易にできたり、メモリの節約になるなど大きなメリットがある。
この機能は自前で作ろうかと考えていたところだったので、非常にありがたかった。しかし、テストを重ねていくうちに、この dbsession を使っているといくつかのセキュリティに関する脆弱性があることがわかった。
それらは「XSS後のセッションハイジャックの予防策が設定でできない」「セッション固定化(のうちのセッションアダプション)ができてしまう」である。
これらの脆弱性対策には、
- アプリ側での防御策をとる(dbsession を外側から拡張することも含む)
- dbsession 側で修正および拡張可能にしてもらう
- dbsession 以外の選択肢をとる(要は使わない)
があると思う。3 つ目も考慮しないといけないけどここでは触れないこととする。
この脆弱性対策は、実は素の Tomcat6 のセッション管理ではクリアできている。
dbsession 機能と、通常の Tomcat6(6.0.35) のセッション管理機能について比較してみる。
dbsession | tomcat6 | |
---|---|---|
セッションクッキーの httpOnly 属性 | 設定できない | server.xml で設定可能 |
セッションアダプション脆弱性 | おこる | おこらない |
大きくこれが挙げられると思う。
Cookie には httpOnly という属性があり、これを On にしておくと、JavaScript からは操作できなくなるため仮に XSS 脆弱性により悪意のある JavaScript が実行される場合でもセッションIDを盗まれる(セッションハイジャックをされる)可能性が下がるということである。生の Tomcat6を使っている場合でもデフォルトではこの設定は有効ではなく server.xml (context.xml) の Context 要素の useHttpOnly 属性を true に明示的に設定しなければならない。
また Tomcat6 では、管理されている(有効なセッションの)セッションID 以外は受け付けないので、セッションアダプション(任意のセッションIDをセッションIDに固定化させること)による脆弱性が起こらないのだ。これは Cookie のセッション、URL につけられたセッションともにである。
さて、個別に見てみるとする。
httpOnly 属性について
これは Cookie クラスのアクセサメソッドでつけることはできないので非常にやっかいである。
Tomcat 自身も内部処理ではCookieオブジェクトを分解して文字列を組み立てた一番後ろに "; httpOnly" を付与している。
アプリでの対応
これはアプリ側で、無理やりだが対応はできる。
それは Filter などでセッションクッキーを探し出して、レスポンスに毎回付与するというやり方である。
キーや値を変えていないとはいえ、毎回更新するのは気持ちが悪い。毎回レスポンスにSet-Cookieヘッダがつくことになる。決してスマートとは言い難い。
アプリでセッションが生成されるタイミングが明確にわかるのならばその処理のレスポンスに付与してもよいかもしれない。
response.setHeader("Set-Cookie", "S2SESSIONID=" + c.getValue() + "; Path=" + c.getPath() + "; HttpOnly");
追記:なぜか↑がうまくいかなくなった・・・(9/18) 家ではうまくいってたのに・・・。
セッションアダプション脆弱性
Tomcat6 でこの脆弱性がおこらないのは、Tomcat6 は自身が発行したセッションID以外は受け付けないからである。
org.apache.catalina.connector.Request#isRequestedSessionIdValid で管理されているセッションIDかどうかを内部でチェックしている。
ところが dbsession では、発行したセッションを一括して管理していないため、リクエストで飛んできたセッションID(Cookie 由来、URL 由来ともに)は、"hoge"
であろうが空文字であろうがそれをセッションIDとしてしまうのである。これはPHPでもおこるらしい(どのバージョンかはよく知りません)がPHPはアプリでセッションIDを再発行できるためセッション固定化はかなり防げるのである。
さて対策だが、Tomcat6 がやっている「未知のセッションIDをうけつけない」か、PHPがやっている「セッションId振り直し」のどちらかで対応したい。
前者は、セッションIDを発行する dbsession 側でないと管理できないだろう。
後者は、Tomcat6 ではできない。だが、dbsession は逆に改良すればできるのではないだろうか?
アプリの対応として、SessionFilterを複製したものを自前でつくり、、、と考えたがSessionIdUtil のフィールドにアクセスできなくなる。
よしんばここがクリアしたとして、次は、S2HttpServletRequestWrapper を継承したクラスを作ろうとおもったが、S2HttpServletRequestWrapper の
フィールドは Private なので子クラスからアクセスできず、自前の対策断念することになった。
こちらも dbsession 側での対応が求められる。僕が対応するなら前者だろうか。Tomcat6 を参考にすることができるし、割と実装がイメージしやすい。
この脆弱性は、気持ち悪いので今すぐにでもなんとか回避したいところなんだが。。。
URL Rewriting によるセッションID付与だけでも禁止する設定があればまだリスクは下がるだろう(Tomcat6 では disableURLRewriting はデフォルトでtrue だが ;jsessionid でつけられた値は、Cookie のセッションIDがない場合はセッションIDとして認識されてしまうので注意。Tomcat7なら TRACKING-MODE を COOKIE のみとすることにより、URLの ;jsessionid を参照しなくできるよう) ええとここでのリスクが下がるというのは、攻撃する側としても Cookie を加工(リクエストヘッダを加工)するよりは URL を加工するほうが簡単というくらいの意味あいです。
結論として、ここはアプリで解決はできなさそう。
できるのであれば教えてほしい。
そもそも dbsession の使い方
dbsession は便利であるがこのあたりの特性をよく利用して使わないといけないと今回いろいろ調べていて強く感じた。
セッション無効化について考えてみる。
セッションが無効化されるタイミングは2つあって、それぞれ
- 明示的にログアウトしたとき(session.invalidate() が呼ばれたとき)
- タイムアウトしたとき
である。Tomcat6 のセッション管理機構を使う場合は、上記の2つはそれぞれサーバ側でセッションが無効化されてしまうのである。
無効化されたあとでもクライアントには古いセッションIDはあるが、それでアクセスしてもTomcat6 は受け付けないので、新しいセッションIDが振られるのである。
しかし、dbsession に関していうと前者の session.invalidate() の際は DB のデータをクリアすることしかしていない。内部的にはそのセッションは無効化されて新しいセッションIDが振られていたりするが、肝心のセッションクッキー(つまりセッションID)はそのままである。次回アクセスしたときに、そのIDが上記セッションアダプションの問題により再利用されてしまう。
セッションクッキーなんてブラウザ落としたら消えるじゃんと思うかもしれないが、最近のブラウザはタブを保存する際にセッションクッキーまで保存しているようであり(おそらくですが)、セッションクッキーは揮発性であるとはもはや考えないほうがいいのかもしれない。
まとめると、dbsession に関してはアプリ側としては
- Cookie の有効期限(max-age)はセッションクッキー(-1)ではなく明示的に1h(3600 s)などと設定する(web.xml) (念のため上記時間にあわせてDBも定期的にクリーニングする。念のためというかデータが増えるのは嫌だから必ず消すべき。)
- session.invalidate と同じタイミングで、Cookie を削除する(有効期限を過去にする)
が今できるベストプラクティスではないかと思う。そうすると見かけ上、ログアウト後もセッションタイムアウト後も新規にセッションIDが振られることになる。
そしてさらに付け加えるなら、上記の httpOnly の設定である。
となるとやはりセッションアダプションだけが気になるのである。
ここは対応していただきたいなぁと思う。
訂正
- Cookie の有効期限(max-age)はセッションクッキー(-1)ではなく明示的に1h(3600 s)などと設定する(web.xml) (念のため上記時間にあわせてDBも定期的にクリーニングする。念のためというかデータが増えるのは嫌だから必ず消すべき。)
- session.invalidate と同じタイミングで、Cookie を削除する(有効期限を過去にする)
1. はダメです。
明示的に時間を設定したときに、仮に1hとすると、この1hは誰も更新してくれないので、普通に1h使い続けていても、突如セッションタイムアウトになることがあります。
問題が元に戻ってしまった。
おそらく、
- このクッキーの max-age をアクセスの都度更新する。
をすれば意味があるかもしれません。
なんだかなぁ・・。
結局こうやった(9/22)
下記でいうところの「セッション無効化」は、
SessionIdUtil.writeCookie(request, response, UUID.create()); session.invalidate();
と考えてほしい。単純に invalidate() するだけでは sessionid が使いまわされることになる。
- Cookie の有効期限(max-age)はセッションクッキー(-1)ではなく明示的に1h(3600 s)などと設定する(web.xml)
- 念のため上記時間にあわせてDBも定期的にクリーニングする。念のためというかデータが増えるのは嫌だから必ず消すべき。
- ログアウト時はセッションを無効化する
- ログイン画面に遷移したタイミングでセッションを無効化する
- 好ましいのはログイン成功時
- パーシステントクッキーなので、誰かが期限を明示的にのばしてあげないといけない。Filter で、Cookie の中身を分解し、文字列化して response.addHeader() した。この際、chain.doFilter(requestWrapper, responseWrapper); の前で実行しないとだめだった。
- ここで httpOnly をつけている。
とまぁこんなかんじである。
リクエストごとにCookieが書き換わるのがなんかアレなかんじである。
それに苦肉の策でログイン画面表示時に無効化しているので、同じブラウザで2枚ひらくともうアウトである。パーシステントなクッキーなのでブラウザのプロセスも関係ない。Cookieは共有される。
ここは後々なんとかしたいところ。
何観たっけ?
スーパー![11]
グロさがいきすぎてて、逆に笑える。でもラストは普通にいい落とし所にもっていったな。
ダークナイト・ライジング[12]
いつもは箕面の109シネマズなんだけど、時間帯の都合でマイカル茨木まで見に行った。
いやぁ、期待していたよりよかったですよ。あまり長さを感じさせません。
ちゃんと理解しようと思うと、ダークナイトだけでなくて、その前の「バットマン・ビギンズ」も見る必要があります。
アン・ハサウェイはあまり好きな女優ではないですけど、この役は良かったとおもいます。
カウボーイ&エイリアン[13]
もうタイトルからしておかしい。
みてみるとそのまま。もうギャグの世界なんだけれど、ダニエル・クレイグが渋すぎることと、ハリソン・フォードが出演しているだけでいい映画に見えてしまうということで、このちゃんちゃらおかしい世界観をそこそこまじめに見ることができる。
つっこみ出すとキリがない映画なんだけど。ほんとに。
オリビア・ワイルドは「トロン」以来だったけど、いや、よくこの役引き受けたよな。。。
しかし、アメリカ人の考える「エイリアン」にはもううんざり。
スーパー8、クローバーフィールド、あのあたりと同じですな。
イヤホン購入
また断線させてしまった。
なので、ヨドバシカメラで770円の同じイヤホンを購入。
僕にとって、イヤホンは消耗品。
パナソニック カナル型イヤホン ブラック RP-HJE150-K
- 出版社/メーカー: パナソニック
- 発売日: 2008/10/15
- メディア: エレクトロニクス
- 購入: 50人 クリック: 134回
- この商品を含むブログ (22件) を見る
ワイルドスピードMEGAMAX [10]
あー、これ続きものだったんだー。
まあ単品でも楽しめる作りになってるけどー。
まぁ何も考えずに楽しめますよ。
ダークシャドウ[9]
劇場で見た。
まーまー、ティム・バートン、ジョニー・デップって感じです。
ドラキュラもの。
クリストファー・リーのカメオ出演には思わず感激してしまった。
ミシェル・ファイファーは相変わらず、年老いても美人ですなぁ。
ノーカントリー[8]
なんだよこれ。何がおもしろいんだ。
どう解釈すればいいんだ?
コーエン兄弟・・・。よくわからん。
ノーカントリー スペシャル・コレクターズ・エディション [DVD]
- 出版社/メーカー: パラマウント ホーム エンタテインメント ジャパン
- 発売日: 2008/08/08
- メディア: DVD
- 購入: 3人 クリック: 80回
- この商品を含むブログ (276件) を見る