BLOG株式会社ラクーンホールディングスのエンジニア/デザイナーから技術情報をはじめ、世の中のためになることや社内のことなどを発信してます。by一緒にラクーンのサービスをつくりませんか?採用情報カジュアル面談2014.1.6インフラサーバネットワークDoS攻撃からサーバーを守る、mod_dosdetector導入事例のご紹介mod_dosdetectordosdetectorApacheDoSこんにちは、羽山です。今回はDoS攻撃の話題です。DELIVERY)にはブラウザからの普通のアクセスを逸脱した過度なHTTPリクエストがしばしばやってきます。大半は独自クローラーなどの悪意がある攻撃とは言えないものですがサイトに与える負荷は高く対応の必要がありました。今回の記事では弊社で行ったDoS攻撃対策の顛末をお送りします。記事の性質上、設定内容など一部ぼやかして表現している点もありますがご容赦ください。当記事では過度なHTTPリクエストをすべてDoS攻撃という呼称で統一しています。DoS攻撃とは?そもそもDoS攻撃と一口に言ってもTCPレベルからHTTPレベルまで様々な手法が存在します。floodという攻撃で、接続開始を表すSYNパケットを攻撃対象に大量に送りつけることでサーバーのリソースを大量に消費させます。幸いなことにTCPレベルのDoS攻撃の多くはFirewallやLoadBalancer(以下LB)に保護機能があることが多く、特別な対策を意識するケースは減ってきています。その一方で問題が顕在化しているのはHTTPレベルのDoS攻撃です。「正常なHTTPアクセスを大量に行う」などステートレスな手法では防げないものが多いため、防御する仕組みや機器は複雑・高度・高価になりがちです。サーバー構成の説明まずは対策を行う弊社のサーバ環境を簡単に説明します。以下の図のようにFirawallとL7のLB以下に複数台のウェブサーバが配置されて、LBから各ウェブサーバはリバースプロキシの構成をとっています。ウェブサーバは主にApache2.2/2.4系を利用しています。専用アプライアンスの導入ある程度の予算がとれるならばDoS対策専用のアプライアンス導入が第一候補として考えられます。多岐にわたるアプライアンスが出そろっていてある程度成熟しているといえるでしょう。シグネチャや振る舞いに基づいた保護を基礎として、SQLインジェクション[1]などのWEBアプリケーションの脆弱性まで保護してくれるものもあります。SQLインジェクションなどはアプリ側できちんと対策する前提ではありますが、アプライアンス導入には安全性を保証する客観的な指標となるメリットがあります。DoS対策アプライアンスは元々L7をベースに動作しているためHTTPプロトコルを詳細に解析するたぐいの機能追加とは相性がよいことから、付加価値を高めるために両者が同時に提供されることがあります。弊社の場合、「まずはDoS対策を導入」というステージだったため、いきなり大きく予算を取るリスクを避けたかったこともありアプライアンスの導入は保留にして別の案を検討しました。次に検討したのは各ノード、つまりウェブサーバのApacheレベルでの検査です。各ノードでの検査の場合はソフトウェアで対応できるので必要な費用を最低限におさえることができます。DELIVERYのサーバはLBがノードを選択する際に持続接続機能を利用していて同一クライアントは同一ノードに転送されるため、ノード側でのDoS検査でも上流での検査とさほど変わらないレベルで実施可能でした。Apache側での防御手段を調べたところmod_dosdetectorというid:stanaka氏が開発・公開してくださっているモジュールがApache2.2/2.4で利用可能で弊社の要件にも近いということが分かりました。LB配下では接続元IPがLBの内部IPに置き換えられてしまい利用できないためX-Forwarded-Forヘッダを代わりに確認する必要がありますが、dosdetectorはその機能にも対応しています。この案を採用してみることになりました。mod_dosdetectorの基本機能すでにいくつものサイトで紹介されているので詳細は省略しますが、基本的には下記のような動作をします。一定時間内のアクセス数が設定した閾値を超えたらSuspectDoSという環境変数に1をセットするmod_dosdetector自体の動作は以上で終わりです。環境変数に入れるだけの動作なのでその後はmod_rewriteなど既存のモジュールで柔軟に処理できます。基本機能はほぼ要件にマッチしたのですが、いくつか解決が必要な課題が出てきました。以下に挙げる点はrewriteで頑張れば回避できるものもありますが、複雑なrewriteルールを避けたかったため、結論としてはmod_dosdetector自体を改修して機能追加することで解決しました。弊社で利用しているモジュールを記事の最下部でダウンロードできるようにしています。LB配下の一般的な環境で使いやすいよう最適化しています、無保証ですが興味ある方はご利用ください。X-Forwarded-ForからのIP抽出方法mod_dosdetectorは接続元IPの代わりにX-Forwarded-Forを参照するDoSForwardedというオプションがあります。基本的にはこの設定で問題ないのですが、X-Forwarded-Forに複数のIPが含まれるパターンで問題となる場合があります。mod_dosdetectorのX-Forwarded-ForからIPの選択方法は下記のようになっています。X-Forwarded-Forヘッダが存在していて、その文字列が有効なIPではなかった場合はDoS検査の対象外になるここで2点問題が出てきますが、その話の前にX-Forwarded-Forヘッダの一般的な動きを理解しましょう。一般的にプロキシサーバやLBを経由するとX-Forwarded-Forの末尾に接続元IPを追加して、接続元IPを自身のIPとした新しいHTTPリクエストを目的のサーバに送信します。通常は社内プロキシで転送する場合はX-Forwarded-Forに内部IPを残さない設定にしますが、設定が不十分でそのまま残ってしまってるケースもよくあります。198.51.100.23となるため、検査対象のIPアドレスを先頭から取得すると正しい検査を行えません。dosdetectorの標準動作である先頭のIPを採用した場合、下記の2つの問題が発生します。X-Forwarded-Forヘッダは容易に偽造可能1つめは不適切なIPアドレスを元にDoS検査を行ってしまうことになり、全く関係のないクライアント同士が同一IPという扱いを受けてしまったり、内部IPを無視する設定を行った場合は検査が作動しなかったりします。DELIVERYネットワークの管理下にあるLBが付与した一番最後のIP以外はすべて信頼できません。それ以外のIPは偽造可能なため例えばX-Forwarded-Forヘッダに毎回異なるIPを適当に設定してアクセスすれば実質DoS検査を無効化できてしまいます。X-Forwarded-Forが存在しない場合X-Forwarded-Forヘッダが存在しなかった場合、dosdetectorは接続元IPを利用してDoS検査を行います。一般的にLBからのリバースプロキシ構成ではX-Forwarded-Forヘッダを常に付与する設定にできるので、LBを経由するアクセスなのにX-Forwarded-Forヘッダが付与されないということはありません。各ノードに流れてくる一部のアクセスにはX-Forwarded-Forヘッダが付与されないことがあります。それらはLBから生存確認のために送るheartbeatであったり、サーバー間通信などシステム内部的なアクセスです。X-Forwarded-Forヘッダがない場合に接続元IPを代わりに利用してしまうと、これらの通信を阻害してしまい問題になることがあります。DoS検査の対象について今回DoS検査の対象としたサーバではいくつかのシステムが並列で稼働していて、そのうち一部をDoS検査から除外する必要がありました。それらはシステムが内部的にAjaxで呼び出すAPIなど、連続で叩かれることが元々想定されているものなどです。根本的にはアクセス頻度の大きく異なるサービスが同一のサーバに同居していることが問題なんですが、レガシーなシステムも含むためあまり手を入れずに対処したいという事情もありました。dosdetectorは検査対象がVirtualHost単位になるため、ディレクトリ毎に対象・除外の設定を行うことはできません。mod_rewriteで除外したいパスを表面上素通しすることは可能ですがDoS検査自体は動作するため、対象外のページでもアクセスがカウントされ、そのまま検査対象のページに移動したらDoS検査に引っかかるなんてことがおきてしまいます。DELIVERYにアクセスします。社内ネットワークのグローバルIPは1つなので簡単にDoS検査に引っかかってしまうことが分かりました。mod_rewriteで省くことは簡単なのですが、mod_rewriteの設定がごちゃごちゃするのでどうせならこれも要件に入れちゃえ!と、ついでに入れました。モジュールの変更ポイント<DoSIgnoreIpRange>※複数行で設定可能DoS検査の対象から除外するIP範囲を指定可能で、下記3種類の指定方法を認識します。DoS検査を行う対象をrequest_uriで絞り込みます。IncludePathが1つ以上ある場合は明示的にIncludePathに指定されたパスのみDoS検査の対象とするIncludePath/ExcludePathともに、複数設定した場合はOR結合です。X-Forwarded-Forに複数IPを含む場合の処理を変更X-Forwarded-Forヘッダの末尾からIP候補を探します。カンマ区切りで順に取得して、IPとして無効な文字列やDoSIgnoreIpRangeに含まれるIPを除外した最初(より末尾に近い)のIPをDoS検査対象とします。X-Forwarded-Forヘッダを偽装されても正しくDoS検査を行うことができます。DoSForwardedがOnでX-Forwarded-Forが存在しない場合の処理を変更DoSForwardedがONの場合は、X-Forwarded-Forが存在しなければDoS検査を行わないよう変更しました。mod_dosdetector.patch同じディレクトリに上記ファイルをダウンロードapxsのパスが異なる場合はMakefileを修正します。以下、パッケージ版のapacheを利用している前提で進めます。/etc/conf/httpd.confモジュールが読み込まれていることを確認します。modules/mod_dosdetector.so具体的な数値や設定を載せるといろいろと怒られるのであくまで設定例としてご紹介します。下記は弊社で実際に運用している設定とは異なります。100DoSPeriodはDoS検出する時間単位で、指定した秒数の間にDoSThreshold回以上のアクセスがあるとSuspectDoS=1がセットされます。DoSHardThreshold回以上のアクセスがあるとSuspectHardDoS=1もセットされます。DoS判定されるとDoSBanPeriodで指定した秒数の間SuspectDoS=1がセットされ続け、その後いったん規制がクリアされます。DoSTableSizeは保持するIP一覧の数で、この数だけ共有メモリに領域が確保されます。そのため多くすれば検出の幅は広がりますが、IP一覧からIPを検索する負荷が上昇するため効率が下がります。IP一覧はLRUで管理されるため頻繁にアクセスしてくるDoS攻撃のIPは残りやすため、よほど高負荷な環境でなければ100もあれば十分だと考えられます。^(image/|application/|text/javascript|text/css)Apacheがローカルで解決できるレベルでコンテンツタイプによる除外を行います。mod_proxyなど外部リソースから動的にコンテンツタイプが返却される場合は除外できないため、この設定に頼ってしまうと危険です。動的コンテンツの場合は後述のmod_rewriteで除外することを検討します。203.0.113.56内部IPと特定のIP(社内ネットワークのグローバルIPを想定)を除外する例です。サーバー間でなんらか直接やりとりしている場合やLBからのheartbeatなどのために内部IPは除外しておいた方が無難です。必要に応じて社内ネットワークのグローバルIPなども除外します。/path1/exclude2DoSIncludePathにはDoS検査対象のパスを前方一致で指定、未指定の場合はすべてのパスが対象となります。DoSExcludePathにはDoS検査対象から除外するパスを前方一致で指定します。DoSExcludePathがDoSIncludePathよりも優先され、両方とも複数のパスを設定した場合はOR結合となります。[E=DoS:1]dosdetectorの結果を受け取ってmod_rewriteで除外する例です。ここではSuspectDoS変数に1がセットされていて、かつユーザーエージェントにgooglebotを含まない場合を最終的なDoS対象としていて、新しい変数DoSに1をセットします。env=DoS最終的なアクションです。DoS=1がセットされている場合は503ページを表示しつつログも出力します。いうパスに存在する前提です。最後に本モジュールを導入して2ヶ月以上経ちますが問題なく安定稼働しています。DELIVERYでもようやく必要性が出てきたところでした。この記事がそういったステージの方の一助になることを願います。非常に有用なモジュールを開発および公開してくださっているid:stanaka氏に感謝いたします。