Apache HTTP Server向けのWebアプリケーションファイアウォール(WAF)「ModSecurity」を使ってみよう

WebサイトやWebアプリケーションのセキュリティを高めるツールの1つに、「Webアプリケーションファイアウォール(WAF)」と呼ばれるものがある。WAFはあらかじめ指定しておいたパターンに該当するリクエストをブロックすることで、脆弱性を狙う攻撃を未然に防ぐソフトウェアだ。今回はWAFの1つであり、Apache HTTP Serverと組み合わせて利用できる「ModSecurity」を紹介する。

2017年3月に公表されたWebアプリケーションフレームワーク「Struts 2」の脆弱性は、国内でも複数のサービスで情報漏洩被害が発生するという大きな問題に発展した。Struts 2については以前よりセキュリティ面での不備が指摘されてはいたものの、今回の脆弱性については公表された数日後に攻撃ツールが出回っている状態となっており、対応が遅かったサイトが狙われたという状況になっている。

また、昨今では公表されていない脆弱性(ゼロデイ脆弱性)を狙った攻撃も発生している。こういった攻撃に対する対策は非常に難しいが、その1つとしてWebアプリケーションファイアウォール(WAF)と呼ばれるツールを導入するという方法がある。

WAFはクライアントからのリクエストを検証し、あらかじめ定義しておいた検出ルールにマッチした場合にそのリクエストを遮断(ブロック)するという処理を行うソフトウェアだ。

たとえばWebサイトを狙った攻撃としてよくあるのが、リクエストに細工した文字列やデータを含めて送信するものだ。たとえば前述のStruts 2では、「Content-Type」などのHTTPヘッダに特別な文字列を入れることで任意の処理を実行できるという脆弱性が狙われた。また、GETやPOSTリクエストのパラメータに細工した文字列を入れて意図しないSQLを実行される「SQLインジェクション」や、任意のコードをサイト内に埋め込む「XSS」などもこれに含まれる。WAFを導入して適切に設定を行っておくことで、こういった攻撃を防ぐことができる。

オープンソースのWAF

WAFとして利用できるソフトウェアは多数存在しており、その多くはセキュリティ企業などが提供する商用製品/商用サービスとなっている。しかし、数は少ないがオープンソースのWAFも存在する。オープンソースのWAFとしてよく知られているのが次の5つだ。

ModSecurity

ModSecurityはApache HTTP ServerやNginx、Microsoft IISのモジュールとして提供されているものだ。このモジュールを組み込むことで、これらWebサーバーをWAFとして利用できるようになる(図1)。

図1 ModSecurityのWebサイト
図1 ModSecurityのWebサイト

活発な開発が続けられており、採用実績も多い。DebianやUbuntu、Red Hat Enterprise Linux(RHEL)およびCentOSといったその互換OSなどでは標準でパッケージが提供されており、導入も容易だ。検出用のルールセットはOWASP ModSecurity Core Rule Set(CRS)としてメンテナンスされており、Apache License 2で提供されている。

NAXSI

NAXSI(Nginx Anti XSS & SQL Injection)はその名のとおり、NginxにXSSやSQLインジェクション対策機能を追加するモジュールだ(図2)。NBS Systemというセキュリティ企業が開発を主導しており、こちらも安定して開発が続けられている。

図2 NAXSIのGitHubリポジトリ
図2 NAXSIのGitHubリポジトリ

WebKnight

WebKnightはWindowsのIIS(およびISAPI対応アプリケーション)に向けたWAFで、AQTRONIX社が開発を主導している(図3)。IIS 5からWindows 10に搭載されているIIS 10までをサポートしており、インストーラ付きで配布されているため導入がしやすい。

図3 AQTRONIXのWebKnightページ
図3 AQTRONIXのWebKnightページ

Shadow Daemon(shadowd)

Shadow Daemon(shadowd)はModSecurityやNAXSI、WebKnightとは異なり、Webアプリケーションにライブラリを組み込んで利用するタイプのWAFだ(図4)。

図4 Shadow DaemonのWebサイト
図4 Shadow DaemonのWebサイト

現在PHPおよびPerl、Python向けのライブラリが提供されており、このライブラリがデーモンとして稼動するshadowdと通信してリクエストの内容をチェックする仕組みになっている。Webアプリケーション側を修正する必要があるが、さまざまなWebアプリケーションで利用できるというメリットがある。

ModSecurityやNAXSIなどと比べると開発ペースはやや低めとなっているものの、プロジェクトはまだ続いているようだ。

IronBee

IronBeeは米セキュリティ企業Qualysが2011年に立ち上げたプロジェクトだ。ModSecurityに関わった開発チームが主導するとされていたが(発表時の報道)、現時点では開発は停滞しており、2015年以降新版のリリースは行われていない。また、公式サイト(https://www.ironbee.com/)についても現在はアクセスできない状態となっている。

WAFを利用するための注意点

前述のとおり、WAFはWebサーバー宛のリクエストの中身をチェックし、定義されている検出ルールにマッチするかどうかでそのリクエストの正当性を判別する。そのため、適切に動作させるには適切な検出ルールを用意することが必要となる。

今回紹介するModSecurityではあらかじめ検出ルールが用意されているが、後述するとおりデフォルトではやや厳しくルールが設定されており、危険性のないリクエストを遮断してしまう可能性も高い。そのため、利用の際にはカスタマイズが必要となる。カスタマイズにはトライアンドエラーが必要となるほか、セキュリティに関する知識も求められる。設定ミスによってWAFの効果を無くしてしまう可能性もあるため、スキルやリソースに不足を感じるのであればセキュリティを専門とする業者に相談することも検討すべきだろう。

また、WAFを導入したとしても、それだけで完璧にすべての攻撃を防げるというわけではない。特に昨今では未知の攻撃手法が頻繁に開発される状況となっている。まずはWebアプリケーションやWebサイト自体のセキュリティ対策をしっかり行ったうえで、それに加えてセキュリティを向上させる追加の手段としてWAFを利用するべきであろう。

ModSecurityを使ってみる

さて、今回は先で挙げた5つのWAFのうち、Apache HTTP ServerとModSecurityの組み合わせについて、その導入方法や設定方法について紹介する。また、本記事後編ではnginxとModSecurity、NAXSIの組み合わせについて紹介する。

なお、本記事では特に言及が無い限り、CentOS 7もしくはUbuntu 16.04 LTSを使用して検証を行っている。CentOS向けの説明についてはRed Hat Enterprise Linux(RHEL)やそのほかのRHEL互換環境、Ubuntu向けの説明についてはDebianでも応用できると思われるが、未検証である点は注意してほしい。

Apache向けModSecurityのインストール

前述の通り、ModSecurityはRHEL/CentOSやFedora、Debian、Ubuntuなどで標準パッケージとして提供されており、yumコマンドやapt-getコマンドでインストールできる。CentOSでのパッケージ名は「mod_security」、Ubuntuでのパッケージ名は「libapache2-mod-security2」となっている。

また、検出用のルールセットであるCRSもそれぞれパッケージ化されており、CentOSでは「mod_security_crs」、Ubuntuでは「modsecurity-crs」というパッケージで提供されている。

CentOSの場合、これらのパッケージをインストールし、必要に応じて設定ファイルを修正した後、httpdサービスを再起動するとModSecurityが有効になる。

# yum install mod_security mod_security_crs
# systemctl restart httpd

Ubuntuの場合も基本的には同様だが、インストール後にa2enmodコマンドで「security2」モジュールを有効にしてからapache2サービスを再起動する必要がある。

# apt-get install  libapache2-mod-security2 modsecurity-crs
# a2enmod security2
# systemctl restart httpd

なお、CentOSとUbuntuでは各種ファイルがインストールされるディレクトリなどが大きく異なるため、設定方法も異なる点に注意したい。

CentOSでの設定

CentOSの場合、/etc/httpd/conf.d/mod_security.confがメインの設定ファイルで、あらかじめデフォルトの「推奨設定」がここに記述されている。個別の検出ルール設定ファイルは/etc/httpd/modsecurity.dディレクトリ以下にインストールされ、このディレクトリ内や/etc/httpd/modsecurity.d/activated_rulesディレクトリ内に格納された設定ファイル(拡張子が「.conf」のファイル)が読み込まれるよう設定されている。

ルールセット(CRS)関連ファイルは/usr/lib/modsecurity.dディレクトリ以下にインストールされ、このディレクトリ内の設定ファイルへのシンボリックリンクが/etc/httpd/modsecurity.d/activated_rulesディレクトリ内に作成される。一部の設定を無効にしたい場合はこのシンボリックリンクを削除すれば良い。

Ubuntuでの設定

Ubuntuの場合、デフォルトでは/etc/apache2/conf-enabled/security2.confファイルによって/etc/modsecurityディレクトリ以下にある拡張子が「.conf」のファイルを読み込むよう設定されている。このディレクトリには推奨される設定が記述された設定ファイルが「modsecurity.conf-recommended」というファイル名でインストールされているので、これを「modsecurity.conf」にリネームするか、もしくはコピーして利用しよう。

また、ルールセット(CRS)関連ファイルについては/usr/share/modsecurity-crsディレクトリ以下にインストールされる。これらについてはデフォルトでは参照されない設定になっているので、/etc/modsecurityディレクトリ内で次のように実行してシンボリックリンクを作成し、これらのファイルが読み込まれるように設定しておこう。

# cd /etc/modsecurity
# ln -s /usr/share/modsecurity-crs/base_rules/* .
# ln -s /usr/share/modsecurity-crs/modsecurity_crs_10_setup.conf .

Ubuntuのデフォルト設定では監査およびログの記録のみを行い、リクエストが不審と判断されてもブロックは行わない設定になっている。これは/etc/modsecurity/modsecurity.conf内の「SecRuleEngine」設定で変更できる。たとえば次のようにこのパラメータの値を次のように「DetectionOnly」から「On」に変更すると、実際にブロックが行われるようになる。

# -- Rule engine initialization ----------------------------------------------

# Enable ModSecurity, attaching it to every transaction. Use detection
# only to start with, because that minimises the chances of post-installation
# disruption.
#
#SecRuleEngine DetectionOnly
SecRuleEngine On

独自の設定ファイルの追加

ModSecurityを利用するにあたって独自の検出ルールを追加したい場合、設定ディレクトリ(CentOSの場合/etc/httpd/modsecurity.d/activated_rules、Ubuntuの場合/etc/modsecurity)内に拡張子が「.conf」の設定ファイルを作成し、そこに設定を記述すれば良い。

たとえば、デフォルトのルールセットではIPアドレスによるアクセスが不正なアクセスとして検出されブロックされてしまう。この挙動はテスト目的での利用などの際には不便だろう。IPアドレスによるアクセスを許可するには、新たに設定ファイルを追加し、そこに次のような内容を記述すれば良い。なお設定ファイル名は適当なもので良いが、今回は「local_00.conf」という名前で作成している。

SecRule REQUEST_HEADERS:Host "^<サーバーのIPアドレス>$" "id:'1011', phase:1,t:none,nolog,pass,ctl:ruleRemoveById=960017"

設定ファイルの変更や追加/削除後にそれを反映させるには、Apache HTTP Serverの再起動が必要となる。

ModSecurityをテストしてみる

ModSecurityを導入したら、まずは正しくModSecurityが動作しているかを検証してみよう。まず、設定したサーバーに「index.html」などの適当なファイルを設置し、そこへのアクセスを行ってみよう。問題なくアクセスできたら、今度は続いてそのURLの末尾に「?union+select」というクエリパラメータを付けてアクセスしてみる。正しくModSecurityが動作していれば、このようなSQLで使われるクエリパラメータ付きのアクセスはブロックされ「403 Forbidden」といった結果になるはずだ。

「WAF Testing Framework」によるテスト

続いて、ModSecurityでどの程度不審なアクセスをブロックできるのかを、「WAF Testing Framework」というWindows上で動作するツールを使ってチェックしてみよう。WAF Testing Frameworkはセキュリティ関連製品やサービスを提供するIMPERVA社が提供している無料ツールで、実際に不正なリクエストをリモートサーバーに送信してその挙動を検証するものだ。

このツールは本来はプロクシ型のWAFを対象にしており、「WebGoat」という典型的な脆弱性が実装されたセキュリティ学習用Webアプリケーションを攻撃対象として利用する。検証対象のWAFでWebGoatが稼動するサーバーを保護するよう設定した上で、そのWebGoatに向けてさまざまなリクエストを送信してその挙動を調べる仕組みだ。

今回はApache HTTP Serverでリバースプロクシ機能を提供する「mod_proxy」を使用し、WAF機能を有効化したApache HTTP Serverを中継してWebGoatにアクセスを行えるよう事前に設定を行っておく。

まずApache HTTP Serverでのプロクシ設定だが、CentOS 7ではデフォルトでmod_proxyが有効になっているので、Apache HTTP Serverの設定ディレクトリである/etc/httpd/conf.d/内に次のようにリバースプロクシ設定を記述した設定ファイルを適当なファイル名(たとえば「proxy.conf」)で作成するだけで完了する。

ProxyRequests Off
ProxyPass /WebGoat http://localhost:8080/WebGoat
ProxyPassReverse /WebGoat http://localhost:8080/WebGoat

ここでは、Apache HTTP Serverの「/WebGoat」宛のアクセスを「http://localhost:8080/WebGoat」に転送するよう設定している。今回はApache HTTP Serverと同じマシン上でWebGoatを動かすことを想定しているが、もし別のマシン上で動かす場合は適切にプロクシ先のURLを変更しよう。

Ubuntuではデフォルトではmod_proxyが有効化されていないため、まずa2enmodコマンドなどでこれを有効化する作業が必要となるが、基本的な設定手順は同様だ。

# a2enmod proxy
# a2enmod proxy_http
# a2enmod proxy_html

WebGoat 5のセットアップ

Apache HTTP Serverの設定変更および再起動が完了したら、続いてWebGoatのセットアップを行う。WebGoatの最新版は現在バージョン8だが、WAF Testing Frameworkではバージョン5というかなり古いバージョンのWebGoatが必要となる。また、WebアプリケーションサーバーのTomcatなども必要となる。そのため、今回は以下のようにDockerを使用してWebGoatを実行することとした。

まず、次のような内容のDockerfileを用意する。

FROM tomcat:7
ADD https://github.com/WebGoat/WebGoat-Archived-Releases/raw/master/WebGoat-5.4/WebGoat-5.4.war /usr/local/tomcat/webapps/WebGoat.war
ADD tomcat-users.xml /usr/local/tomcat/conf/tomcat-users.xml

また、次のような内容の「tomcat-users.xml」ファイルをDockerfileと同じディレクトリに作成する。

<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
  <role rolename="webgoat_basic"/>
  <role rolename="manager"/>
  <role rolename="tomcat"/>
  <role rolename="standard"/>
  <role rolename="admin"/>
  <role rolename="server_admin"/>
  <role rolename="role1"/>
  <role rolename="webgoat_admin"/>
  <role rolename="webgoat_user"/>
  <user username="tomcat" password="tomcat" roles="tomcat"/>
  <user username="both" password="tomcat" fullName=""/>
  <user username="webgoat" password="webgoat" roles="webgoat_admin"/>
  <user username="basic" password="basic" roles="webgoat_user,webgoat_basic"/>
  <user username="admin" password="admin" roles="admin,manager"/>
  <user username="guest" password="guest" roles="webgoat_user"/>
  <user username="server_admin" password="server_admin" roles="server_admin"/>
  <user username="role1" password="tomcat" roles="role1"/>
</tomcat-users>

これら2つのファイルを用意したら、次のように「Docker build」コマンドを実行してコンテナを作成する。

# docker build -t webgoat-5.4 .

作成したコンテナは、次のようにして起動できる。

# docker run --name webgoat -d -p 8080:8080 webgoat-5.4

コンテナの起動に成功したら、Webブラウザから次のURLにアクセスする。正しくWebGoatが起動していれば、ユーザー名「guest」、パスワード「guest」でログインできるはずだ。

http://<コンテナを稼動させているサーバー>:8080/WebGoat/attack

なお、WebGoatには脆弱性学習用ツールという位置付けのため、実際に脆弱性が含まれている。そのため、検証が終わったら「docker stop」コマンドで適宜コンテナを終了させておくことをおすすめする。

# docker stop webgoat

WAF Testing Frameworkのインストールと実行

続いてはWAF Testing Frameworkのインストールだ。このツールはIMPERVA社のダウンロードページから入手できる。この際、氏名やメールアドレスなどの入力が必要となる。登録を行うとメールでダウンロードURLが送られてくるので、Webブラウザ等でダウンロードしよう。

ダウンロードしたアーカイブを展開すると「WAFTesting.exe」という実行ファイルが含まれているので、これを実行する(図5)。

図5 アーカイブ中の「WAFTesting.exe」を実行する
図5 アーカイブ中の「WAFTesting.exe」を実行する

WAF Testing Frameworkを起動すると、対象ホストやポート、設定フィルなどを指定するウィンドウが表示される(図6)。

図6 WAF Testing Frameworkのメインウィンドウ
図6 WAF Testing Frameworkのメインウィンドウ

ここでは「Host」にWebGoatが稼動しているサーバー、「TCP Port」にポート番号を入力する。また、「Block Patterns File」にはWAFTesting.exeと同じディレクトリ内にある「custom_blocking.xml」を指定する。この指定を忘れるとWAF Testing Frameworkがブロックされたことを正しく検出できないので注意したい。

それでは、まずは比較のためにApache HTTP Serverを経由させずに直接WebGoatに対してテストを実施してみよう。この場合、ポート番号として8080を指定すれば良い。

実行が完了すると、WAF Testing Frameworkと同じディレクトリ内にレポートPDFが出力される。ここでまず注目したいのが、「False Negatives and False Positives」という項目だ(図7)。

図6 WAFを利用しない場合の「False Negatives and False Positives」グラフ
図7 WAFを利用しない場合の「False Negatives and False Positives」グラフ

「False Negatives」(偽陰性)は、本来は問題のあるリクエストとしてブロックすべきものを、実際には問題であると判断できずにブロックしなかった割合だ。また、「False Positives」(偽陽性)は本来は問題のないリクエストであるにも関わらず、誤って問題のあるものと判断してブロックしてしまった割合だ。今回の場合はWAFを経由しておらず、一切ブロックを行わないため前者は「100%」、後者は「0%」となっている。

続いて、ポート番号として80番を指定し、ModSecurityによるWAF経由でアクセスするようにしてテストを行った結果が図8だ。

図7 WAFを利用した場合の「False Negatives and False Positives」グラフ
図8 WAFを利用した場合の「False Negatives and False Positives」グラフ

この場合、False Negativesは0%となっており、問題のあるクエストについてはそのすべてをブロックできているという結果となっている。ただし、False Positivesと判断されたリクエスト(本来は問題ないリクエストでであるにも関わらずブロックされたリクエスト)の割合は50%と高い。

出力されたレポートでは、どういったリクエストがFalse Positivesとして認識されたかも記載されている。たとえば今回の例では、POSTリクエストのBody内に「select」や「from」という文字列が含まれているだけでブロックされていることなどが分かる(図9)。

図8 問題があると誤判定されたリクエスト一覧も出力される
図9 問題があると誤判定されたリクエスト一覧も出力される

ModSecurityの検出ルールをカスタマイズする

さて、このようにModSecurity向けに提供されている標準ルールセット(CRS)は、かなり「厳しめ」な設定となっており、正常なリクエストを危険なものと誤検出してブロックするケースが多く発生する。そのため、実際に利用する場合はこれらをカスタマイズする必要がある。続いてはこのようなカスタマイズの方法を説明しよう。

監査ログを読む

ModSecurityのデフォルト設定では、攻撃であると判断したリクエストについてはそれを監査ログに記録する。デフォルトの監査ログはCentOSの場合/var/log/httpd/modsec_audit.log、Ubuntuでは/var/log/apache2/modsec_audit/logとなっている。

次の例は、あるブロックされたリクエストに対する監査ログだ。ある1つのリクエストに対する一連の監査ログは「--<ID>-A--」から始まり、「--<ID>-Z--」で終わるようになっている。

--23ad2c2a-A--
[16/Aug/2017:22:31:49 +0900] WZRJRRnlHDkUyAkIPqd9sgAAAAM 118.109.127.172 10483 <ホストのIPアドレス> 80
--23ad2c2a-B--
POST /WebGoat/attack?Screen=2634&menu=900 HTTP/1.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Authorization: Basic Z3Vlc3Q6Z3Vlc3Q=
Content-Type: application/x-www-form-urlencoded
Referer: http://<ホストのIPアドレス>/WebGoat/attack?Screen=2634&menu=900
Content-Length: 105
Host: <ホストのIPアドレス>
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)

--23ad2c2a-C--
title=&message=I+think+it+better+to+select+one+from+this+basket+%28as+this+one+is+bigger%29&SUBMIT=Submit
--23ad2c2a-F--
HTTP/1.1 403 Forbidden
Content-Length: 216
Keep-Alive: timeout=5, max=95
Connection: Keep-Alive
Content-Type: text/html; charset=iso-8859-1

--23ad2c2a-E--

--23ad2c2a-H--
Message: Access denied with code 403 (phase 2). Pattern match "(?i:(?:,.*?[)\\da-f\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98][\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98](?:[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98].*?[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98]|\\Z|[^\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98]+))|(?:\\Wselect.+\\W*?from)|((? ..." at ARGS:message. [file "/etc/httpd/modsecurity.d/activated_rules/modsecurity_crs_41_sql_injection_attacks.conf"] [line "209"] [id "981257"] [msg "Detects MySQL comment-/space-obfuscated injections and backtick termination"] [data "Matched Data:  select one from found within ARGS:message: I think it better to select one from this basket (as this one is bigger)"] [severity "CRITICAL"] [tag "OWASP_CRS/WEB_ATTACK/SQL_INJECTION"]
Action: Intercepted (phase 2)
Apache-Handler: proxy-server
Stopwatch: 1502890309129885 1995 (- - -)
Stopwatch2: 1502890309129885 1995; combined=1651, p1=185, p2=1430, p3=0, p4=0, p5=36, sr=10, sw=0, l=0, gc=0
Response-Body-Transformed: Dechunked
Producer: ModSecurity for Apache/2.7.3 (http://www.modsecurity.org/); OWASP_CRS/2.2.9.
Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.1e-fips
Engine-Mode: "ENABLED"

--23ad2c2a-Z--

さらに、「--<ID>-<分類>--」というセパレータでログが内容的に区切られるようになっている。IDはリクエスト固有の文字列で、「分類」はそのセパレータに続くログの内容を示すものだ(表1)。

表1 監査ログの分類
文字 説明
A 監査ログヘッダ
B リクエストヘッダ
C リクエストボディ
D 未実装
E 中間的なレスポンスボディ
F 最終的なレスポンスボディ
G 未実装
H ログ(本文)
U リクエストボディ
J アップロードされたファイルの情報
K マッチしたルールの情報
Z ログの終了を示す

この例では、「A」(監査ログヘッダ)、「B」(リクエストヘッダ)、「C」(リクエストボディ)、「F」(レスポンスボディ)、「H」(ログ本文)という情報がそれぞれ記録されている。このなかで重要なのが「--<ID>-H--」に続いて記録されているログ本文だ。今回の例では以下の部分が該当する。

--23ad2c2a-H--
Message: Access denied with code 403 (phase 2). Pattern match "(?i:(?:,.*?[)\\da-f\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98][\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98](?:[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98].*?[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98]|\\Z|[^\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98]+))|(?:\\Wselect.+\\W*?from)|((? ..." at ARGS:message. [file "/etc/httpd/modsecurity.d/activated_rules/modsecurity_crs_41_sql_injection_attacks.conf"] [line "209"] [id "981257"] [msg "Detects MySQL comment-/space-obfuscated injections and backtick termination"] [data "Matched Data:  select one from found within ARGS:message: I think it better to select one from this basket (as this one is bigger)"] [severity "CRITICAL"] [tag "OWASP_CRS/WEB_ATTACK/SQL_INJECTION"]
Action: Intercepted (phase 2)
Apache-Handler: proxy-server
Stopwatch: 1502890309129885 1995 (- - -)
Stopwatch2: 1502890309129885 1995; combined=1651, p1=185, p2=1430, p3=0, p4=0, p5=36, sr=10, sw=0, l=0, gc=0
Response-Body-Transformed: Dechunked
Producer: ModSecurity for Apache/2.7.3 (http://www.modsecurity.org/); OWASP_CRS/2.2.9.
Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.1e-fips
Engine-Mode: "ENABLED"

ここにはそのリクエストがなぜ不適切と判断されたのかについてが記載されており、この例ではPOSTされた値のうち、「message」パラメータの内容が「/etc/httpd/modsecurity.d/activated_rules/modsecurity_crs_41_sql_injection_attacks.conf」という設定ファイルの209行に記載されているルールにマッチしたためにブロックされたことが分かる。

続いて該当の設定ファイルを見てみると、ここでは次のようなルールが記載されている。

SecRule REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|REQUEST_COOKIES_NAMES|ARGS_NAMES|ARGS|XML:/* "(?i:(?:,.*?[)\da-f\"'`´’‘][\"'`´’‘](?:[\"'`´’‘].*?[\"'`´’‘]|\Z|[^\"'`´’‘]+))|(?:\Wselect.+\W*?from)|((?:select|create|renad|alter|delete|update|insert|desc)\s*?\(\s*?space\s*?\())" "phase:2,capture,t:none,t:urlDecodeUni,block,msg:'Detects MySQL comment-/space-obfuscated injections and backtick termination',id:'981257',tag:'OWASP_CRS/WEB_ATTACK/SQL_INJECTION',logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}',severity:'2',setvar:'tx.msg=%{rule.id}-%{rule.msg}',setvar:tx.sql_injection_score=+1,setvar:tx.anomaly_score=+%{tx.critical_anomaly_score},setvar:'tx.%{tx.msg}-OWASP_CRS/WEB_ATTACK/SQLI-%{matched_var_name}=%{tx.0}'"

ここで使われている設定ルール「SecRule」は、次の書式でルールを定義するディレクティブだ。

SecRule <対象> <検査方法> <アクション>

より詳しい内容はドキュメントを参照して欲しいが、ここでは対象として下記が指定され、Cookieの中身やGETおよびPOSTリクエストにおけるパラメータ名やその値、ペイロード内のXMLなどを対象とするよう設定されている。

REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|REQUEST_COOKIES_NAMES|ARGS_NAMES|ARGS|XML:/*

また、検査方法としては次の正規表現が指定されている。この正規表現は、「select」などSQL文を含む文字列にマッチする。

(?i:(?:,.*?[)\da-f\"'`´’‘][\"'`´’‘](?:[\"'`´’‘].*?[\"'`´’‘]|\Z|[^\"'`´’‘]+))|(?:\Wselect.+\W*?from)|((?:select|create|renad|alter|delete|update|insert|desc)\s*?\(\s*?space\s*?\())

最後のアクション部分は下記のようになっている。やや分かりにくいが、指定した対象が正規表現にマッチした場合にブロックを行うとともに、監査ログに「msg:」で指定したメッセージを出力するよう指定している。

phase:2,capture,t:none,t:urlDecodeUni,block,msg:'Detects MySQL comment-/space-obfuscated injections and backtick termination',id:'981257',tag:'OWASP_CRS/WEB_ATTACK/SQL_INJECTION',logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}',severity:'2',setvar:'tx.msg=%{rule.id}-%{rule.msg}',setvar:tx.sql_injection_score=+1,setvar:tx.anomaly_score=+%{tx.critical_anomaly_score},setvar:'tx.%{tx.msg}-OWASP_CRS/WEB_ATTACK/SQLI-%{matched_var_name}=%{tx.0}'

これらから、この設定項目はパラメータやその値にSQLのような文字列が含まれるリクエストすべてを、問題のあるリクエストとしてブロックするものであることが分かる。

特定の検出ルールを無効化する

さて、確かにSQLインジェクション攻撃はSQL文字列を含んだ内容をリクエストすることで行うのだが、この設定では「select」などの単語を含んだリクエストがすべてブロックされてしまう。これによってSQLインジェクションはブロックできるものの、ユーザーが該当の単語を含む文字列を投稿できなくなってしまうという問題がある。そこで、続いてはこの設定に引っかかったものについてはブロックしないよう設定を追加してみよう。

前述のとおり、設定の追加はModSecurityの設定ディレクトリ(CentOSの場合/etc/httpd/modsecurity.d/activated_rules、Ubuntuの場合/etc/modsecurity)内に拡張子が「.conf」の設定ファイルを作成し、そこに設定ディレクティブを記述することで行う。今回はlocal_00.confという設定ファイルを用意した。

特定のルールを無効化するには、まず対象とするルールのIDを特定しておく必要がある。IDは監査ログに「[id "<ID>"] 」のような形で出力されている。また、ルール設定ディレクティブでは「id:'<ID>'」のように設定されている。ログから分かるとおり、今回の例の場合IDは「981257」だ。

--23ad2c2a-H--
Message: Access denied with code 403 (phase 2). Pattern match "(?i:(?:,.*?[)\\da-f\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98][\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98](?:[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98].*?[\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98]|\\Z|[^\"'`\xc2\xb4\xe2\x80\x99\xe2\x80\x98]+))|(?:\\Wselect.+\\W*?from)|((? ..." at ARGS:message. [file "/etc/httpd/modsecurity.d/activated_rules/modsecurity_crs_41_sql_injection_attacks.conf"] [line "209"] [id "981257"] [msg "Detects MySQL comment-/space-obfuscated injections and backtick termination"] [data "Matched Data:  select one from found within ARGS:message: I think it better to select one from this basket (as this one is bigger)"] [severity "CRITICAL"] [tag "OWASP_CRS/WEB_ATTACK/SQL_INJECTION"]

追加する設定の内容だが、ModSecurityでは特定の条件にマッチしたリクエストのみを対象とする「SecRule」ディレクティブと、すべてのリクエストを対象とする「SecAction」ディレクティブが用意されており、これらを利用して新たなルールを追加すれば良い。今回はすべてのリクエストを対象に指定した検出ルールを無効化するよう、次のような設定を追加した。

SecAction "id:'1101', phase:1,t:none,pass,ctl:ruleRemoveById=981257"

設定についての詳細はドキュメントを確認して欲しいが、ここで指定されている各パラメータは次のような意味となっている。

  • 「id:'1101'」:この設定自体のIDを指定するもので、ほかのルールと重複しない限り適当なものを選んでよい
  • 「phase:1」:リクエストヘッダを検出するPhase1でこの処理を実行する
  • 「t:none」:データの変換などを行わないことを明示的に指定
  • 「pass」:ルールにマッチしてもブロックなどの処理は行わずにほかのルールのマッチを続ける
  • 「ctl:ruleRemoveById=981257」:ModSecurityの設定を変更し、指定したIDのルールを無効化する

なお、前述のとおりIPアドレスによるアクセスをブロックしない追加設定を行っていた場合は、それに追加して上記の1行を追加すれば良い。

この設定を追加した後、Apache HTTP Serverを再起動して再度WAF Testing Frameworkによるテストを実行した結果が図10だ。

図9 設定変更後のWAF Testing Frameworkによるテスト結果
図10 設定変更後のWAF Testing Frameworkによるテスト結果

False Negativesの割合は0%のままだが、False Positiveの割合が「50%」から「47%」に減少しており、この変更によって誤判定が減っていることが分かる。

対象システムによって設定をカスタマイズすることが重要

さて、このようにModSecurityを導入することで、問題のあるリクエストをブロックすることが可能になる。ただ、前述の通りModSecurityとともに提供されている設定ルール(CRS)はやや厳しめの設定となっているため、これを利用する場合は設定のカスタマイズが必須となる。

CRSは対象とする内容毎に別ファイルにまとめられており、たとえばSQLインジェクションについては「modsecurity_crs_41_sql_injection_attacks.conf」というファイルに、XSS関連については「modsecurity_crs_41_xss_attacks.conf」というファイルにまとめられている。必要に応じてこれら設定ファイルのシンボリックリンクを削除することで、設定をまとめて無効化することも可能ではある。ただし、もちろんその場合セキュリティは弱くなる。たとえば「modsecurity_crs_41_sql_injection_attacks.conf」ファイルのシンボリックリンクを削除してSQLインジェクション関連の設定を無効化した場合、誤検出であるFalse Positivesの割合は大きく減少したが、そのいっぽうで問題のあるリクエストをブロックできなかったFalse Negativesの割合も増加している(図11)。

図10 SQLインジェクション関連の設定をすべて無効化した場合のテスト結果
図11 SQLインジェクション関連の設定をすべて無効化した場合のテスト結果

また、今回テストに使用したWAF Testiing Frameworkはあくまでテストツールの1つであり、このツールで問題がないと判断されたからといった安全であるというわけでもない。設定を支援するための一つの指標にはなるものの、ツールを過信しないよう注意したい。

さて、ModSecurityはApache HTTP Serverだけでなく、nginxでも利用が可能だ。本記事の後編ではnginxで利用できるWAFについて紹介していく。