Webアプリケーションプラットフォームに対する攻撃手法として、少し前から話題に上っているhashdos攻撃というものがあります。
データさえ準備できれば容易に多くのCPUリソースを消費させることが可能なこの攻撃に対して、Tomcat環境ではどのように対策を行えばよいかを簡単にまとめておきます。
(2012/02/08 追記)
PHPではhashdos対策バージョンに別の脆弱性が存在するようなので、PHPでhashdos対策を行おうとしている方はこちらを参照してください。
PHPにリモートユーザーが任意のコードを実行できる脆弱性 | MorphMorph
(2012/01/20 追記)
Tomcatの場合、ハッシュ値の衝突が起きなくともそもそも大量のパラメータの処理自体に弱いという脆弱性もあったようですね。
Apache Tomcat – Apache Tomcat 7 vulnerabilities # Fixed in Apache Tomcat 7.0.23
Apache Tomcat – Apache Tomcat 6 vulnerabilities # Fixed in Apache Tomcat 6.0.35
Apache Tomcat – Apache Tomcat 5 vulnerabilities # Fixed in Apache Tomcat 5.5.35
National Vulnerability Database (NVD) National Vulnerability Database (CVE-2012-0022)
いずれにしても、早めに最新版に更新してmaxParameterCountを適切に設定しておいた方がよさそうです。
hashdos攻撃について
hashdos攻撃について簡単に説明します。取り急ぎTomcatでの対策のみを行いたい方は、このセクションを読み飛ばしてください。
ハッシュテーブル
ハッシュテーブル(hash table)とは、キーと値とを対応付けて格納するデータ構造のことで、キーとなるデータ(多くの場合は文字列)をもとに生成されたハッシュ値を使用して格納場所を決定することで、対応する値への素早いアクセスを可能としているものです。
単純な配列に多数のデータを格納し、重複がないようにデータを追加したり、データが既に存在するかどうかを調べるような場合、(ソート等を行わない限り)既に配列に格納されている全データを走査するような処理が必要となります。
これに対しハッシュテーブルではデータを格納する配列(厳密には配列でない場合もあるが、ここでは配列とする)を複数用意し、それぞれの配列に各データを分散させて格納することで一度に走査しなければならないデータの数を減らすような構造となっており、格納先の配列を選択するためにキーのハッシュ値が使用されることになります。(キーから生成されたハッシュ値が同一であるデータ同士は同一の配列に、ハッシュ値が異なるデータ同士は別々の配列に格納されることになる)
hashdos攻撃とは
ハッシュテーブルは、Javaのjava.util.HashMapやPHPの連想配列等多くの言語にて実装されており、リクエストに含まれるパラメータを解析し保持する際にこの構造を用いることが、Webアプリケーションプラットフォームでは一般的となっているようです。(例えば“http://hogehoge.com/fuga?a=1&b=2&c=3”というリクエストを受けた場合、a, b, c をキーとして対応する 1, 2, 3 の値をハッシュテーブルに格納する)
ここで、各パラメータの名前(テーブルに格納する際のキーとなる)から生成されるハッシュ値が全て同一であった場合はどうなるでしょうか。全てのデータが内部的には同一の配列に格納されることになり、ハッシュテーブルの構造的なメリットが活かせないことになってしまいます。
もちろん、通常のリクエストではそのようなケースは稀なのですが、ハッシュ値の算出方法が判っている場合は意図的にハッシュ値が同一となるパラメータ名を列挙することが可能であり、そのようなパラメータ名を大量に含んだリクエストを送りつけることにより、パラメータの解析時に巨大な配列に対する走査を繰り返し行わせることで、CPUリソースを食い潰させてしまおうというのが、hashdosと呼ばれている攻撃手法です。
その他、hashdos攻撃を受けた場合の影響の詳細やApacheやPHP等での対処方法について詳しく書かれている方がいらっしゃいますので、リンクをはっておきます。
徳丸浩さんの日記: Webアプリケーションに対する広範なDoS攻撃手法(hashdos)の影響と対策
Tomcat環境でのhashdos対策
Tomcat環境でhashdos攻撃への対策を行うには、現時点(2012/01/11現在)での最新バージョンである、7.0.23または6.0.35へのバージョンアップが必要となります。(5.5系については現時点で対策バージョンは存在しない模様)
(2012/01/20 追記)
5.5系についても2012/01/16に対策版である5.5.35がリリースされているようです。
動作確認はしていませんが、ドキュメントを見る限りでは他のバージョンと同様maxParameterCountとFailedRequestFilterが追加されています。
Tomcat公式ページ
また、単にバージョンアップするだけでもある程度の効果はあるのですが、攻撃された場合の被害を最小限に抑えるために、きちんと設定を行っておきましょう。
maxParameterCount値の設定
最新バージョンでは各Connectorの実装にmaxParameterCountという属性が追加されており、ここで指定した上限値を超える数のパラメータがリクエストに含まれる場合に、その旨の警告文をログに出力すると共に、以降のパラメータの処理を行わないようになっています。
ただ、この属性が未指定の場合のデフォルト値は10,000となっており、通常のWebアプリケーションではそこまで多くのパラメータを必要とすることはあまりないと思いますので、運用しているWebアプリケーションで必要なパラメータ数を満たしつつ、出来るだけ小さな値を指定しておくようにしましょう。
例として、デフォルトのserver.xmlでのHTTPコネクタの設定にmaxParameterCountとして256を指定した場合は次のようになります。(AJPコネクタを使用している場合はそちらにmaxParameterCountを指定すること)
(2012/02/10 追記)
この内容はあくまで設定例です。下記のConnector要素を丸ごとコピーや置換はせず、現在使用しているHTTPまたはAJPコネクタの設定にmaxParameterCount属性を追記するようにしてください。protocol属性の変更やuseBodyEncodingForURI属性の追記等、デフォルト設定から変更している場合も多いと思いますので、それらの設定を無効にしてしまわないよう注意しましょう。
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" maxParameterCount="256" />
maxParameterCountを指定した場合、その値を超えるパラメータが無視されると共に警告ログが出力されるようになりますが、処理自体は中断されずにリクエストオブジェクトに特別な属性値が設定された上でそのままサーブレット等が呼び出されます。この動作や設定される属性値は、例えば以下のようなサーブレットを作成する等して確認できます。(あくまで動作確認用のテストコードなのでそのまま使用しないこと)
package test; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.catalina.Globals; @WebServlet(urlPatterns={"/test"}) public class TestServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doCommon(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doCommon(req, resp); } private void doCommon(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/plain"); PrintWriter writer = resp.getWriter(); writer.println(req.getParameterMap()); writer.println(Globals.PARAMETER_PARSE_FAILED_ATTR + " : " + req.getAttribute(Globals.PARAMETER_PARSE_FAILED_ATTR)); writer.flush(); writer.close(); } }
このコードではGlobals.PARAMETER_PARSE_FAILED_ATTRの値をキーにしてリクエストの属性値を取得していますが、この値はパラメータ数がmaxParameterCountを超えて切り捨て処理が行われた場合にtrueに設定されるようです。
FailedRequestFilterの使用
制限値を超えた場合に、処理を続行させずに即エラーとしてレスポンスを返したい場合もあると思いますが、そのような場合にはmaxParameterCountと同じく最新版で追加されたorg.apache.catalina.filters.FailedRequestFilterを使用します。
このフィルタは、リクエストにGlobals.PARAMETER_PARSE_FAILED_ATTRの属性値が設定されているかどうかをチェックして、設定されていればステータスコード400(Bad Request)として処理を終了するだけの実装となっており、Tomcatに付属しているデフォルトのweb.xmlに含まれる以下のコメントアウト部分をコメント解除して有効化することで使用できます。
この内容は手元のTomcat7.0.23付属のweb.xmlにて確認したものです。バージョンにより異なる可能性がありますので、必ず使用対象バージョンのTomcatに付属するweb.xmlファイルやドキュメント等を確認してください。
<!-- <filter> <filter-name>failedRequestFilter</filter-name> <filter-class> org.apache.catalina.filters.FailedRequestFilter </filter-class> <async-supported>true</async-supported> </filter> --> : <!-- <filter-mapping> <filter-name>failedRequestFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> -->
最後に
以上、Tomcatでのhashdos攻撃対策についてのまとめでした。この攻撃は実行が容易な上に強力な攻撃ですので、まだ対策していない方は早めに行っておいた方がよいと思います。