HDIV (Http Data Integrity Validator)の話2

4月の日記にも少し書きましたが、実際にHDIVを使ってみたので日記に書きます。

Unified Application Security | Hdiv Security

概要

HDIVでは、Webブラウザからサーバに送られてくるデータを「editable」と「non-editable」に分けています。editableはテキストボックスなどでユーザが自由に入力してくるデータです。non-editableはhiddenやプルダウン、Cookieなど、値が固定だったり一定の範囲に収まっていることが期待されるものです。

例えば、以下のHTMLがあった場合、赤字の部分はnon-editable、青字の部分がeditableです。

<body>
<a href="/test/foo.do?a=111&amp;b=222">link</a>
<form method="post" action="/test/bar.do">
ユーザID:<input type="text" name="userid" value="xxx">
<br>
性別:
<input type="radio" name="gender" value="1">男</input>
<input type="radio" name="gender" value="2">女</input>
<br>
<input type="hidden" name="admin" value="0">
<input type="submit" value="登録">
</form>
</body>

HTMLには現れませんが、Cookieの値もnon-editableです。

HDIVが備えるいくつかの機能のうち、もっとも重要なのはnon-editableなデータが改竄されることを防ぐ(検知する)機能だと思います。

hiddenやプルダウン、リンクのURLに埋め込まれたパラメータは、XSSSQL Injectionなどのインジェクション系の欠陥や、価格の改竄や権限のエスカレーションなどのロジック的な欠陥を生みやすい部分だと思いますが、HDIVはこれらの攻撃(の一部)を防いでくれるのです。

HDIVは、Strutsを使用しているアプリケーションに導入することができます。導入に際して、既存のアプリケーションを変更する必要はありません。そういう意味で、WAFと似たような感じで導入することができます(機能面でもWAFに近い)。

改竄を検知する方法

改竄を検知するためには、改竄前の正規の値が判っていなければなりません。

HDIVは、HTMLをブラウザに返すときに、リンクなどの遷移先と遷移する際に送られるnon-editableなデータの「正規の値」を全て記憶しておきます。次のリクエストでは、記憶された値と実際に送られてきた値を突合せして改竄を検知します。

正規の値を記憶する方式は、①memory ②hash ③cipher の三種類から選択します(hdiv-config.xmlの「strategy」で指定する)。

「memory」の場合は、HDIVは以下のような動作をするようです。

レスポンスを返す時
  1. HDIVは、レスポンスHTMLを解析して、そのページからの遷移先と遷移の際のnon-editableデータを抽出する。
  • 抽出したnon-editableデータを格納するオブジェクトを作り、そのオブジェクトに「78-0-1189260182702」のようなIDを与える(遷移先単位でIDが振られる)。
  1. そのオブジェクトデータはサーバ側のセッション変数に格納する。
  2. GETやPOST(hidden)のパラメータでIDを次のページに引き継ぐように、レスポンスのHTMLを書き換える。

例えば、HTMLを以下のように書き換えます。

<a href="/test/foo.do?a=111&amp;b=222&amp;_HDIV_STATE_=78-0-1189344658169">link</a>
<form method="post" action="/test/bar.do">
...
<input type="hidden" name="_HDIV_STATE_" value="78-1-1189344658169">
</form>

オブジェクトのIDは、_HDIV_STATE_という名前でHTMLのリンクURLパラメータやフォームのhiddenに埋め込まれます。

なお、レスポンスの解析・書き換えは、TaglibにHDIV独自の処理を追加する方法で行なっているようです(HTMLをParseしているのではないということ)。確認していませんが、JSPでTaglibの使用をサボっている箇所があれば、うまく機能しないと思われます。

リクエストを処理するとき
  1. GETやPOSTで渡される_HDIV_STATE_から、セッション変数内に保存されているオブジェクトを引っ張ってくる。
  2. セッションに保存されているオブジェクトのデータと、そのリクエストで実際に送られてきているデータを比較する。
  3. 遷移先URLの不正、パラメータの過不足、値の改竄があればエラー画面に飛ばす。
memory以外の方式

「cipher」の場合、セッション変数にはnon-editableデータのオブジェクトのID(_HDIV_STATE_)だけが保存されるようです。オブジェクトの中身は、シリアライズ・暗号化されてGET/POSTで引き回されます。

「hash」は「cipher」とほぼ同じですが、「hash」ではnon-editableデータのハッシュをセッション変数に記録するようです。もしかしたら、cipher方式だと暗号化データの改竄や再送などに弱いので「hash」方式が用意されているのかもしれません。

_HDIV_STATE_トークンの構造

_HDIV_STATE_の値は「78-0-1189344658169」のような構造になっています。ハイフンで区切られた最初部分(78)はページ遷移していくたびに1ずつ増えていきます。次の1桁の数値はHTML内の遷移先(リンクやフォーム)の番号、最後の13桁の数値はミリ秒までのUNIXTIMEだと思われます。

このように、_HDIV_STATE_の値を第三者が推測するのは比較的容易なためCSRF対策の役割は果たしません(_HDIV_STATE_とセッションの結びつきはされています)。

しかしHDIVにはCSRF対策のためのオプションもあります。hdiv-config.xml の randomName という項目を true に設定すると、_HDIV_STATE_というトークンの変数名が乱数になります。

履歴情報

_HDIV_STATE_トークンは、ワンタイム性を持っていません。そのため、トランザクショントークンのように、二度押し/リロード対策に使うことはできません。

違う言い方をすると、HDIVはブラウザのリロード機能を妨げません。それに、ブラウザのバックボタンも使用できます。

バックができるということは、以前に参照したページで使用した_HDIV_STATE_とそれにヒモ付くnon-editableデータの履歴がセッション変数に保存されているということです(memory方式の場合)。つまりユーザがページ遷移を行なうたびに、セッションデータが増えていくことになります。

余りに増えすぎないように、過去の履歴を何世代までセッションに保存するかを、applicationContext.xml内で指定することができます。また、memoryだけではなく、hashやcipher方式を選択することでもセッション領域を節約できます。

改竄防止機構の副作用

HDIVは上記のような方法で改竄防止をしている訳ですが、そのような方式を取ると当然副作用も出てきます。

外部サイトからリンクがはれない

まず一つ目の副作用としてあげたいのは、外部サイトからリンクが機能しなくなるというものです。

リンクが機能しなくなるのは、HDIVで改竄検知を行なっている画面では、リクエストの際に然るべき _HDIV_STATE_ とセッションID Cookieとが送られて来なければ、HDIVがリクエストをはじいてしまうからです。

もしサイト全てのページにHDIVを導入した場合、初めてのユーザはセッションIDも_HDIV_STATE_も持っていない状態なので、サイトのどのページにもアクセスできません。これを防ぐために、hdiv-config.xml の userStartPages で、改竄検知対象から除外するページを指定することができます。

この入口となるページ以外には、外部から遷移できません。イントラ上のサイトやログイン以降の機能などのサイトの一部分のみで、HDIVを使えということになると思います。

クライアントサイドでの処理

もう一つの副作用は、以前の日記でも触れましたが、余りダイナミックなクライアントサイドでの処理ができなくなることです。

HDIVでは、hiddenの値やプルダウンの「正規の値」を、サーバからHTMLが出て行く時点のHTMLで判断しているため、例えば画面上のプルダウンが変更された際に別のプルダウンの中身をJavaScriptで動的に書き換えるような処理をしていると、次のリクエストのときにHDIVは「改竄発生!」と認識してしまいます。

その他の機能

簡単にHDIVのその他の機能についても書いておきます。

Confidentiality

hdiv-config.xml の Confidentiality 設定を true にすると、例えばHTML内に、http://www.host.com/?data1=12&data2=24 のようなリンクがあった場合、リンクのURLは http://www.host.com/?data1=0&data2=1 に変換されます。さらに、http://www.host.com/?0=0&1=1 のようにパラメータの名前も隠蔽することができます。

上記はリンクの例ですが、hiddenなどnon-editableなデータは全て同じように隠蔽することができます。

editableデータの検証

これまで記述した機能は、non-editableなデータを対象としたものでした。テキストボックスなどのeditableなデータに対してHDIVが提供するのは、データ検証の機能です。

hdiv-validations.xml に、検証を適用するページのURLと、許可あるいは拒否する値のパターンを正規表現で定義します。あらかじめ用意されているパターンはありません。開発者が自分のサイトに必要なパターンとURLを定義して使用するというものです。

おわりに

最初に私が持った印象は、既存のアプリの欠陥はそのままで、欠陥を覆い隠すようなやり方でセキュリティ対策を行なうというような、ややネガティブなものでした。

しかし、実際のところHDIVを使うとユーザの操作は非常に強く制限されるので、アプリケーションの欠陥が発現するのが防げることは多いように思います。

よりポジティブに考えると、はじめからHDIV(に限らずその手のもの)に積極的に頼って、楽してアプリを開発しようという発想もあると思います。

例えば、プルダウンやラジオボタンなどの値の検証処理のような煩雑な仕事はHDIVがやってくれるわけですから、そのような仕事はHDIVに任せておこうというような考え方です。

データをセッション変数に保存するのも、HDIVを使うと簡単です。hiddenに入れてやればよいだけで、複数ウィンドウを開いたときにセッション変数の値が上書きされておかしくなるような問題も出ません。

ただ、HDIVのサイトのフォーラムなどを見る限り、余り活発な動きは無さそうに見えます。実際の導入事例がどれくらいあるのかも判りませんし、まだ商用サイトには導入しづらいように思います(一応コマーシャルサポートはあるようですが)。