技術者ブログ
クラウド型WAF「Scutum(スキュータム)」の開発者/エンジニアによるブログです。
金床“Kanatoko”をはじめとする株式会社ビットフォレストの技術チームが、“WAFを支える技術”をテーマに幅広く、不定期に更新中!

ScutumのゼロデイHashDoS対策と、JavaのXMLパーサ実装
はじめに
数年前に発見されたHashDoSですが、実際に攻撃が行われているケースは殆ど目にしません。ソフトウェアのロジックを突くDoSとしては、他にApacheに対してRangeヘッダを使う攻撃手法(Apache Killer)なども発見されましたが、こちらも同様に、殆ど実際の攻撃としては検知していません。DoS攻撃にはもっと単純で、攻撃対象のアーキテクチャを調べる必要のない、シンプルな物量攻撃が好まれているような印象です。そのため筆者としてはHashDoSのセキュリティリスクは非常に低いものという認識です。
さて現在、国内ではあまりニュースになっていませんが、いくつかのミドルウェアにおいてHashDoS対策の漏れが発見されており、対策の実装が進められているフェーズのようです。これらのミドルウェアにおけるHashDoS攻撃の対策をScutumでは既に実施済みで、「ゼロデイ攻撃への対策」ができている状態です。情報がパブリックになった時点で、また続報をお届けします。
Javaでの対策
今回、あらためていくつかの場面でのHashDoS対策について調べているうちに、いくつか面白い実装を見かけたので、このエントリで簡単に紹介したいと思います。まずJavaですが、普通にJVMを起動すると、HashMapやHashTableがHashDoSに脆弱であることがわかっています(これは仕様です)。しかしjdk.map.althashing.thresholdというシステムプロパティを使うことで対策ができる、とOracleのサイトで説明されています。HashMapクラスの実装において、特に、実際の攻撃に使われそうであるStringクラスについてのみハッシュ値計算を特別に行っていたりするのが興味深いです(JDK1.7で確認)。
Java XMLパーサ(Xerces)での対策
Javaに含まれているXMLパーサの実体は、ApacheのソフトウェアであるXercesです。内部の実装において、JavaのHashMap等を使ってもよさそうな場面において、独自実装のクラスであるSymbolTable等が使われています。SymbolTableではHashDoSへの対策として、こちらの実装がなされています。このSymbolTableクラスの実装は面白いもので、ハッシュ値の衝突を監視し、ある閾値を超えたらハッシュ値の計算ロジックを変更する形になっています。それぞれのインスタンス内部でランタイムにおいてこの処理が行われるので、攻撃者がパフォーマンス劣化をモニタリングしながら衝突を起こそうとブルートフォース的に攻撃しても、それをある時点で無効にすることができる、優れた実装になっています。
テストコード
上記の2点について、テストするためのコードを書いてみました。実行するためにはXercesのライブラリ(jarファイル群)が別途必要になります。
ハッシュ値が衝突する文字列を大量に用意し、それをSymbolTableやHashSetに追加していき、どの程度の時間がかかるかを出力します。上記の対策が施されていないバージョンのXercesでテストすると、出力は以下のようになります。
Testing SymbolTable... 0 ms 29 ms 69 ms 90 ms 127 ms 146 ms 181 ms 212 ms 248 ms 251 ms 281 ms 310 ms 345 ms 374 ms 402 ms 435 ms 449 ms 160 ms 162 ms 202 ms 200 ms 240 ms 271 ms 312 ms 311 ms 361 ms 409 ms 452 ms 529 ms 598 ms 689 ms 678 ms 771 ms TOTAL: 10903 ms
対策済みのバージョンでは、以下のようになります。
Testing SymbolTable... 0 ms 22 ms 49 ms 76 ms 104 ms 121 ms 148 ms 174 ms 198 ms 195 ms 219 ms 237 ms 272 ms 286 ms 315 ms 340 ms 385 ms 356 ms 344 ms 98 ms 2 ms 1 ms 0 ms 1 ms 1 ms 0 ms 1 ms 1 ms 0 ms 1 ms 1 ms 0 ms 1 ms TOTAL: 3951 ms
途中でハッシュ値の計算ロジックが変更され、いきなり速くなっていることがわかります。
次にHashSetについてです。jdk.map.althashing.thresholdの指定なしだと、下記のように時間がかかります。
Testing HashSet... 1 ms 6 ms 13 ms 20 ms 29 ms 35 ms 43 ms 51 ms 59 ms 66 ms 74 ms 81 ms 89 ms 98 ms 106 ms 113 ms 121 ms 131 ms 138 ms 156 ms 158 ms 182 ms 188 ms 218 ms 233 ms 273 ms 405 ms 402 ms 316 ms 351 ms 376 ms 410 ms 509 ms TOTAL: 5851 ms
しかしjdk.map.althashing.thresholdの指定を行うと、下記のように速くなります。
Testing HashSet... 0 ms 4 ms 2 ms 2 ms 1 ms 2 ms 1 ms 3 ms 1 ms 2 ms 2 ms 1 ms 1 ms 1 ms 1 ms 0 ms 1 ms 1 ms 0 ms 0 ms 1 ms 1 ms 0 ms 1 ms 0 ms 2 ms 0 ms 0 ms 0 ms 1 ms 0 ms 0 ms 0 ms TOTAL: 35 ms
まとめ
今回はJavaとXercesにおける、それぞれ異なったHashDoS対策について紹介しました。ご感想などあればお気軽に@kinyukaまでお寄せください。