nginx向けのWebアプリケーションファイアウォールを試す

前編ではApache HTTP Server向けのWebアプリケーションファイアウォール(WAF)であるModSecurityを紹介したが、後編となる本記事ではnginxでModSecurityを利用する方法や、nginx専用のWAF「NAXSI」について紹介する。

記事前編ではWAFの概要やApache HTTP ServerとModSecurityを組み合わせて利用する場合の導入・設定方法、テストについて紹介した。後編となる本記事では、近年シェアを増やしているWebサーバー「nginx」で利用できるWAFについて紹介しよう。

なお、ModSecurityについて詳しくは記事前編を参照してほしい。また、検証環境は前編と同様CentOS 7およびUbuntu 16.04 LTSだ。

nginxでModSecurityを利用する

ModSecurityはApache HTTP Serverと組み合わせて利用するだけでなく、nginxと組み合わせても利用できる。ただしnginx向けのバイナリパッケージは提供されておらず、nginxとともにソースコードから自分でビルドする必要がある。また、現時点では動作がまだ安定していないとされている点には注意したい。

ModSecurityとnginxのビルド

ModSecurityのソースコードは、公式サイトのダウンロードページから入手が可能だ。このソースコードをビルドするには、CurlやPCRE、libxml2などの開発用パッケージのインストールが必要となる。ビルド設定によっても代わるが、CentOS環境の場合次のパッケージをインストールしておく必要があった。

libcurl-devel
httpd-devel
pcre-devel
libxml2-devel
openssl-devel
zlib-devel
perl-devel
libxslt-devel gd-devel
perl-ExtUtils-Embed
GeoIP-devel

また、Ubuntu環境では次のパッケージが必要だった。

libpcre3-dev
libxslt1-dev
libgd-dev
libgeoip-dev
zlib1g-dev
libssl-dev
libxml2-dev
libperl-dev

これらパッケージのインストール後、ダウンロードしたModSecurtyのアーカイブを展開し、展開したディレクトリ中で次のように「--enable-standalone-module」オプション付きでconfigureスクリプトおよびmakeコマンドを実行しておく。

$ wget https://www.modsecurity.org/tarball/2.9.2/modsecurity-2.9.2.tar.gz
$ tar xvfz modsecurity-2.9.2.tar.gz
$ cd modsecurity-2.9.2
$ ./configure --enable-standalone-module
$ make

続いてnginxのダウンロードページからnginxのソースコードアーカイブをダウンロードして適当なディレクトリに展開し、「--add-module=<ModSecurityアーカイブを展開したディレクトリ>/ngix/modsecurity/」オプション付きでconfigureスクリプトを実行する。たとえばModSecurityアーカイブを展開したディレクトリが「../modsecurit-2.9.2」だった場合、以下のようになる。

./configure \
    --add-module=../modsecurity-2.9.2/nginx/modsecurity \
    --pid-path=/run/nginx.pid \
    --lock-path=/run/lock/subsys/nginx \
    --user=nginx \
    --group=nginx \
    --with-ipv6 \
    --with-http_ssl_module \
    --with-http_v2_module \
    --with-http_realip_module \
    --with-http_addition_module \
    --with-http_xslt_module=dynamic \
    --with-http_image_filter_module=dynamic \
    --with-http_geoip_module=dynamic \
    --with-http_v2_module \
    --with-http_realip_module \
    --with-http_addition_module \
    --with-http_xslt_module=dynamic \
    --with-http_image_filter_module=dynamic \
    --with-http_geoip_module=dynamic \
    --with-http_sub_module \
    --with-http_dav_module \
    --with-http_flv_module \
    --with-http_mp4_module \
    --with-http_gunzip_module \
    --with-http_gzip_static_module \
    --with-http_random_index_module \
    --with-http_secure_link_module \
    --with-http_degradation_module \
    --with-http_slice_module \
    --with-http_stub_status_module \
    --with-http_perl_module=dynamic \
    --with-mail=dynamic \
    --with-mail_ssl_module \
    --with-pcre \
    --with-pcre-jit \
    --with-stream=dynamic \
    --with-stream_ssl_module

この設定オプションはRHELやCentOS向けの拡張パッケージ集であるEPELで配布されているnginxのバイナリパッケージのビルドオプションを参考にしている。

続いてmakeコマンドを実行すれば、ModSecurityサポート付きのnginxバイナリが作成される。最後にmake installコマンドを実行してnginxをインストールすれば良い。

$ make
# make install

このとき、デフォルト設定ではnginxは/usr/local/nginx/sbinディレクトリにインストールされる。

$ /usr/local/nginx/sbin/nginx -V
nginx version: nginx/1.12.1
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-11) (GCC)

ModSecurityで利用できるルールセット(OWASP ModSecurity Core Rule Set(CRS)はバイナリパッケージで提供されているものがそのまま利用可能なので、事前にインストールしておこう。

CentOSの場合
# yum install mod_security_crs
Ubuntuの場合
# apt-get install  modsecurity-crs

ModSecurityの設定

nginxでModSecurityを利用する場合、ModSecurityの設定ファイルを用意したうえで、nginxの設定ファイル内にModSecurityを有効化する設定やModSecurityの設定ファイルの指定を追加する必要がある。

まずModSecurityの設定ファイルだが、今回は次のように/usr/local/nginx/modsecurityというディレクトリを作成し、そこにModSecurityの設定ファイルやCRS由来の設定ファイルを格納する。

# mkdir /usr/local/nginx/modsecurity
# cd /usr/local/nginx/modsecurity
↓推奨設定ファイルなどをコピー
# cp  <ModSecurityのソースアーカイブ展開ディレクトリ>/modsecurity.conf-recommended modsecury.conf
# cp  <ModSecurityのソースアーカイブ展開ディレクトリ>/unicode.mapping .
# mkdir activated_rules

↓CRSのファイルなどをコピー(CentOSの場合)
# cp /etc/httpd/modsecurity.d/modsecurity_crs_10_config.conf .
# cd activated_rules
# ln -s /usr/lib/modsecurity.d/base_rules/* .

↓CRSのファイルなどをコピー(Ubuntuの場合)
# cp /usr/share/modsecurity-crs/modsecurity_crs_10_setup.conf .
# cd activated_rules
# ln -s /usr/share/modsecurity-crs/base_rules/* .

また、ここで作成したmodsecurity.confファイルの設定では「SecRuleEngine DetectionOnly」という設定が含まれており、この設定では不審なアクセスのブロックが行われない。そのため、このパラメータの値を以下のように「On」に変更しておこう。

# 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

次に、nginxの設定ファイルが格納されたディレクトリ(今回は/usr/local/nginx/conf)内にModSecurityの設定ファイルをロードするための「modsecurity.conf」という設定ファイルを用意する。このファイルには次のような内容を記述しておく。

Include /usr/local/nginx/modsecurity/*.conf
Include /usr/local/nginx/modsecurity/activated_rules/*.conf

最後に、nginxの設定ファイル(今回は/usr/local/nginx/conf/nginx.conf)内にModSecurityを有効化するための次のような設定を用意する。

location / {
    root   html;
    index  index.html index.htm;
    ModSecurityEnabled on;
    ModSecurityConfig modsecurity.conf;
    error_log logs/modsecurity.log;
}

今回はルートディレクトリ以下で有効にするため「location /」設定内にこれらを追加したが、もちろん他のロケーションを対象に設定することも可能だ。

以上の作業が終わったら、nginxを起動してアクセスしてみよう。まずWebブラウザでnginxが稼動しているホストにアクセスし、正常にアクセスできることを確認する(図1)。

図1 正常にアクセスできる場合のレスポンス

続いて、同じホストに対し末尾に「?union+select」という文字列を付けたURLでアクセスしてみよう。この場合、アクセスがModSecurityによってブロックされ、「403 Forbidden」というレスポンスになるはずだ。

図2 末尾に「?union+select」を付けるとブロックされる

nginx+ModSecuriy構成でWAF Testing Frameworkによるテストを行う

さて、続いてはModSecurityを有効にしたnginxに対し、前編で紹介したテストツール「WAF Testing Framework」によるテストを行ってみよう。テストのための具体的な環境設定については前編を参照してほしい。また、nginxをプロクシとして動作させるには、「location / {}」内に次のように「proxy_pass」項目を追加すれば良い。

location / {
    proxy_pass http://localhost:8080/;
    #root   html;
    #index  index.html index.htm;
    ModSecurityEnabled on;
    ModSecurityConfig modsecurity.conf;
    error_log logs/modsecurity.log;
}

また、前編で紹介したとおりデフォルト設定ではIPアドレスによるアクセスがブロックされるので、必要に応じてIPアドレスによるブロックを解除する設定を追加しておこう。

WAF Testing Frameworkを実行する際の注意点だが、配布アーカイブに含まれている「custom_blocking.xml」ファイルはApache HTTP Server向けの設定となっている。そのため、nginxを対象としてテストする場合は別途設定ファイルを用意する必要がある。今回は次のような内容を含む「nginx_blocking.xml」というファイルを作成して使用した。

<?xml version="1.0" encoding="UTF-8"?>

<blocking-indications>
    <!-- mod-security default error page -->
    <error-page>
        <status-code value="403"/>
        <page-content><![CDATA[Forbidden]]></page-content>
    </error-page>  
</blocking-indications>

このファイルをWAF Testing Frameworkの「Block Patterns File」で指定して実行すれば良い。

さて、以上の設定を行ったあとに行ったテスト結果は図3のようになった。

図3 nginx+ModSecurityでのWAF Testing Frameworkテスト結果

False Negativesが0%となっているのはApache HTTP Serverの場合と同じだが、False Positivesについては微妙に異なる結果となっている。

検出ルールの設定

nginxと組み合わせた場合でも、ModSecurityの設定方法は基本的にはApache HTTP Serverで利用した場合と同じで、記事前編で紹介したとおりのやり方で行える。ただし、デフォルト設定では監査ログの出力先が/var/log/modsec_audit.logとなっている点には注意しよう。

NAXSIを使ってみる

続いては、nginx向けのWAFであるNAXSIについて紹介しよう。

NAXSIはModSecurityとは異なり、リクエストを検査した結果に「スコア」を付け、そのスコアがあらかじめ設定した値を上回ったらそのリクエストをブロックする、という仕組みになっている。そのため、ModSecurityと比べてあらかじめ用意されている設定ルールは少なく、また未知の攻撃もある程度は検出できるという特徴がある。

NAXSIのインストール

NAXSIはnginx向けのモジュールとして提供されているが、ModSecurityをnginxで利用する場合と同様、これを利用する場合にはnginxを独自にビルドする必要がある。具体的な手順は次のとおりだ。

まず、NAXSIのソースアーカイブをGitHub上にあるNAXSIのリポジトリのリリースページからダウンロードし、適当なディレクトリで展開する。

$ wget https://github.com/nbs-system/naxsi/archive/0.55.3.tar.gz
$ tar xvzf 0.55.3.tar.gz

続いてnginxをビルドする。nginxのビルド方法については前述のModSecurityの場合と同じで、ビルドに必要なパッケージを事前に導入しておいた上でnginxのダウンロードページからダウンロードしたソースアーカイブを展開し、「--add-module=<NAXSIアーカイブを展開したディレクトリ>/naxsi_src/」オプション付きでconfigureスクリプトを実行する。

$ wget https://nginx.org/download/nginx-1.12.1.tar.gz
$ tar xvzf nginx-1.12.1.tar.gz
$ cd nginx-1.12.1
$ ./configure \
    --add-module=../naxsi-0.55.3/naxsi_src/ \
    --pid-path=/run/nginx.pid \
    --lock-path=/run/lock/subsys/nginx \
    --user=nginx \
    --group=nginx \
    --with-ipv6 \
    --with-http_ssl_module \
    --with-http_v2_module \
    --with-http_realip_module \
    --with-http_addition_module \
    --with-http_xslt_module=dynamic \
    --with-http_image_filter_module=dynamic \
    --with-http_geoip_module=dynamic \
    --with-http_v2_module \
    --with-http_realip_module \
    --with-http_addition_module \
    --with-http_xslt_module=dynamic \
    --with-http_image_filter_module=dynamic \
    --with-http_geoip_module=dynamic \
    --with-http_sub_module \
    --with-http_dav_module \
    --with-http_flv_module \
    --with-http_mp4_module \
    --with-http_gunzip_module \
    --with-http_gzip_static_module \
    --with-http_random_index_module \
    --with-http_secure_link_module \
    --with-http_degradation_module \
    --with-http_slice_module \
    --with-http_stub_status_module \
    --with-http_perl_module=dynamic \
    --with-mail=dynamic \
    --with-mail_ssl_module \
    --with-pcre \
    --with-pcre-jit \
    --with-stream=dynamic \
    --with-stream_ssl_module

configureスクリプトの実行に成功したら、makeコマンドでビルドの実行やインストールを行う。

$ make
# make install

問題がなければこれで/usr/local/nginx以下にnginxがインストールされるはずだ。

NAXSIの設定

NAXSIの設定はnginxの設定ファイル(nginx.conf)で行う。また、それとは別にNAXSIがリクエストの検査に利用するための検査ルールを定義した設定ファイルも別途必要だ。

まずNAXSIの検査ルール設定ファイルだが、NAXSIの配布ソースアーカイブ内のnxsi_configディレクトリ内に「naxsi_core.rules」という名前でデフォルトの設定ファイルが用意されているので、今回はこれをnginxの設定ファイルディレクトリである/usr/local/nginx/confディレクトリ内にコピーして利用する。

# cp <NAXSIの配布アーカイブを展開したディレクトリ>/naxsi_config/naxsi_core.rules /usr/local/nginx/conf/

また、nginxの設定についてはNAXSIの配布ソースアーカイブ内のnxsi_srcディレクトリ内に「nginx.conf.example」として設定例が含まれているのでそれを参考にする。今回はデフォルトで用意されるnginx.confを次のように修正した。

まず、「http {}」内にNAXSIの検査ルール設定ファイルを読み込むよう「include /usr/local/nginx/conf/naxsi_core.rules;」という内容を追加する。

http {
    include       /usr/local/nginx/conf/naxsi_core.rules;
    include       mime.types;
    default_type  application/octet-stream;

次に、NAXSIを有効にするロケーション(今回は「/」)にNAXSIを有効にするための「SecRulesEnabled;」ディレクティブやログファイルの出力先設定(「error_log」)、ブロック時に表示するファイル(「DeniedUrl」)、ブロックするスコアのしきい値(「CheckRule」)を追加する。

location / {
    root   html;
    index  index.html index.htm;
    SecRulesEnabled;
    error_log logs/naxsi.log;
    DeniedUrl /50x.html;

    CheckRule "$SQL >= 8" BLOCK;
    CheckRule "$RFI >= 8" BLOCK;
    CheckRule "$TRAVERSAL >= 4" BLOCK;
    CheckRule "$EVADE >= 4" BLOCK;
    CheckRule "$XSS >= 8" BLOCK;
}

ここではログファイルとしてログディレクトリ(/usr/local/nginx/logs)以下の「naxsi.log」を指定し、またブロック時は「/usr/local/nginx/html/50x.html」を表示するよう指定している。また、スコアのしきい値は「SQL」および「RFI」、「XSS」については8、「TRAVERSAL」および「EVADE」についてはスコアが4に設定している。

この状態で次のようにnginxを起動し、Webブラウザで正常にアクセスできるか確認してみよう。

# /usr/local/nginx/sbin/nginx

また、正常にアクセスできたら、今度はそのURLの末尾に「?union+select」のようなSQLのキーワードを付与して再度アクセスし、ブロックされることを確認しておこう。

NAXSIのログフォーマット

デフォルト設定ではNAXSIのログは次のようなフォーマットで出力される。

2017/08/21 19:56:18 [error] 10989#0: *1 NAXSI_FMT: ip=<クライアントのIPアドレス>&server=<サーバーのホスト名>&uri=/&learning=0&vers=0.55&total_processed=2&total_blocked=1&block=1&cscore0=$SQL&score0=8&zone0=ARGS&id0=1000&var_name0=, client: <クライアントのIPアドレス>, server: localhost, request: "GET /?union+select HTTP/1.1", host: "<ホスト名>"

このログには、いくつかのパラメータとその値が含まれていることが分かる。それぞれの意味は表1のとおりだ。

表1 NAXSIの監査ログに含まれるパラメータ
パラメータ名 説明
ip クライアントのIPアドレス
server サーバーのホスト名
uri リクエストされたURI
learning NAXSIが「学習モード」で動作していれば1
vers NAXSIのバージョン
total_processed 今までに処理されたリクエストの合計数
total_blocked 今までにブロックされたリクエストの合計数
block リクエストがブロックされていれば1
cscore0 スコア名
score0 スコア
zone0 ゾーン
id0 マッチした検査ルールのID
var_name0 問題があると判断されたパラメータ/変数名
client クライアントのIPアドレス
server サーバーのホスト名
request リクエスト本体
host Host:ヘッダの情報

ここで「ゾーン」はリクエスト中で検査対象となった部分を示しており、「ARGS」ならURLに与えられた引数(GETリクエストの引数)を示している(詳しいドキュメント)。また、この例ではidが1000、cscore0がSQL、score0が8となっていることから、IDが「1000」という検出ルールによって「SQL」というスコアが8に設定され、それによってブロックが行われていることが分かる。

NAXSIに対しWAF Testing Frameworkによるテストを実行する

さて、続いてはデフォルト設定のNAXSIに対しWAF Testing Frameworkによるテストを実行してみよう。手順としてはnginx+ModSecuriy構成の場合とほぼ同様で、nginx.confにリバースプロクシ設定(「proxy_pass http://localhost:8080/;」)を追加してnginx経由でWebGoatにアクセスできるようにし、その状態でnginxにテストリクエストを送信している。この場合の「location /」設定は次のようになる。

location / {
    #root   html;
    #index  index.html index.htm;
    proxy_pass http://localhost:8080/;

    SecRulesEnabled;
    DeniedUrl /50x.html;
    error_log logs/naxsi.log;

    CheckRule "$SQL >= 8" BLOCK;
    CheckRule "$RFI >= 8" BLOCK;
    CheckRule "$TRAVERSAL >= 4" BLOCK;
    CheckRule "$EVADE >= 4" BLOCK;
    CheckRule "$XSS >= 8" BLOCK;
}

今回のNAXSIの設定ではアクセスがブロックされた場合は/50x.htmlというファイルを返すようになっているで、WAF Testing Framework用にこれに対応する設定ファイルを用意する必要がある。今回は次の内容を含む「naxsi_blocking.xml」というファイルを作成して使用した。これをWAF Testing Framework実行時に「Block Patterns File」で指定すれば良い。

<?xml version="1.0" encoding="UTF-8"?>

<blocking-indications>
    <!-- mod-security default error page -->
    <error-page>
        <status-code value="200"/>
        <page-content><![CDATA[An error occurred]]></page-content>
    </error-page>  
</blocking-indications>

さて、NAXSIを利用した場合のテスト結果だが、図4のようになった。

図4 デフォルト設定のNAXSIを対象としたテスト結果

この結果では「False Positives」(偽陽性、危険でないリクエストをブロックしてしまった)は5%と少ない一方、「False Negatives」(偽陰性、ブロックできなかったリクエスト)は50%となっている。

NAXSIの検査ルールをカスタマイズする

実際にNAXSIを運用環境で利用する場合、False PositivesやFalse Negativeを0に近づけるよう設定ルールのカスタマイズを行うことが必要となる。具体的な設定方法や文法などについてはドキュメントで説明されているが、実際の作業としては、送信されたリクエストやログを付き合わせて不足しているルールを追加したり、一部のルールを緩和すると行ったものになる。

また、NAXSIを開発するNBS SystemのGitHubリポジトリでは、WordPressやDrupalなど、いくつかのよく使われるWebアプリケーションやCMS向けの設定ルールが公開されてり、。これらも参考になるだろう。

さらに、NAXSIではこれらの作業を支援するための「nxtool」というツールが提供されている。続いてはこのツールの利用法を紹介しよう。

nxtoolのインストール

nxtoolはNAXSIのソースアーカイブにも含まれているが、最新版はGitHub上で「nxtool-ng」として配布されている。こちらはバックエンドとしてelasticsearchというデータベースエンジンを使用するので、まずはelasticsearchを利用できるようにしておこう。バイナリパッケージなどからelasticsearchをインストールしても良いが、今回はDockerを利用して導入した。この場合、次のようにしてイメージをダウンロードして実行するだけでelasticsearchが利用可能になる。

# docker pull elasticsearch
# docker run -d --name elastic elasticsearch

コンテナを起動したら、次のようにしてそのコンテナに割り当てられたIPアドレスを確認しておこう。この例の場合「172.17.0.2」となる。

# docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' elastic 
172.17.0.2

ちなみにelasticsearchが正しく稼動しているかどうかは、次のようにcurlコマンドで「<IPアドレス>:9200」にアクセスすることで確認できる。次の例のようにelasticsearchの情報がJSON形式で表示されればOKだ。

$ curl -XGET 172.17.0.2:9200/
{
  "name" : "umKGrjZ",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "jtgWIMbdRv-LzqHOtXDZsg",
  "version" : {
    "number" : "5.5.1",
    "build_hash" : "19c13d0",
    "build_date" : "2017-07-18T20:44:24.823Z",
    "build_snapshot" : false,
    "lucene_version" : "6.6.0"
  },
  "tagline" : "You Know, for Search"
}

続いてnxtoolの実行に必要なライブラリなどをインストールする。nxtoolはPythonで実装されており、いくつかの外部ライブラリに依存する。これらのインストールにはPythonのパッケージマネージャであるpipを利用するので、事前にyumコマンドやapt-getコマンドなどでインストールしておこう。また、パッケージのインストール時にPythonの開発者用パッケージが必要となる場合があるので、こちらも同時にインストールしておく。

↓CentOSの場合
# yum install python2-pip python-devel
↓Ubuntuの場合
# apt-get install python-pip python-dev

続いて、次のように「pip install」コマンドで「elasticsearch-dsl」および「python-pcre」、「python-dateutil」、「GeoIP」パッケージをインストールする。なお、ここではユーザーのホームディレクトリ以下にパッケージをインストールするよう「--user」オプションを指定している。

$ pip install --user elasticsearch-dsl python-pcre python-dateutil GeoIP

これらに加えてGeoIPライブラリが使用するGeoIPデータベースファイルも必要だ。これらは次のようにしてインストールできる。

↓CentOSの場合
# yum install GeoIP-data
↓Ubuntuの場合
# apt-get install geoip-database-extra

続いてnxtoolおよび依存するライブラリ(「nxapi」)をGitHubからクローンする。

$ mkdir nxtool-test
$ cd nxtool-test
$ git clone https://github.com/nbs-system/nxapi-ng
$ git clone https://github.com/nbs-system/nxtool-ng

また、nxtoolがnxapiを参照できるようシンボリックリンクを作成しておく。

$ cd nxtool-ng/
$ ln -s ../nxapi-ng/nxapi .

以上の作業が完了したら、nxtool-ngディレクトリ内にある「nxtool.py」を「-h」オプション付きで実行してみよう。エラーが表示されなければインストールは完了だ。

$ python nxtool.py -h
usage: nxtool.py [-h] [-v] [--elastic-source] [--flat-file FLAT_FILE]
                 [--stdin] [--archive] [--elastic-dest] [--typing]
                 [--whitelist] [--slack] [--filter FILTER]
                 [--filter-regexp FILTER_REGEXP] [--stats]
                 [hostname]

Sweet tool to help you managing your naxsi logs.

positional arguments:
  hostname

optional arguments:
  -h, --help            show this help message and exit
  -v, --verbose

Log sources:
  --elastic-source
  --flat-file FLAT_FILE
  --stdin
  --archive

Log destinations:
  --elastic-dest

Actions:
  --typing
  --whitelist
  --slack
  --filter FILTER
  --filter-regexp FILTER_REGEXP
  --stats

nxtoolの設定は、nxtool.pyと同じディレクトリ内にある「config.cfg」ファイルで行う。このファイルを編集し、elasticsearachのホスト名(IPアドレス)やバージョンなどを次のように修正しておこう。elasticsearchのバージョンは、今回のようにDocker経由で起動した場合は「6」を指定すれば良い。

[elastic]
host = <elasticsearchが稼動しているホストのIPアドレス>:9200
use_ssl = False
index = nxapi
version = <elasticsearchのバージョン>

nxtoolの使い方

nxtoolを利用するには、まず「--flat-file <ログファイル>」オプションおよび「--elastic-dest」オプション付きでnxtoolを実行してログデータをelasticsearchに投入する。

$ python nxtool.py --flat-file /usr/local/nginx/logs/naxsi.log --elastic-dest

続いて「--elastic-source --stat」オプション付きでnxtoolを実行すると、ログの簡単な統計情報が出力されるはずだ。

$ python nxtool.py --elastic-source --stat
# IP #
119.243.164.45: 206

# URI #
/annonce.php: 1
/adm/krgourl.php: 2
/admin/modules/modules/forum/admin.php: 1
/admin/modules/blocks.php: 1
/ase.php: 1
/base.php: 1
/index.php: 2
/WebGoat/attack: 180
/config.php: 1
/chart.php: 1

# ZONE #
BODY: 163
ARGS: 43

# SERVER #
133.242.6.157: 206

また、「--filter」などのオプションを使うことで表示する統計情報を絞り込める。詳しくはREADMEなどを参照して欲しいが、たとえば「--filter 'uri=<URI>'」オプションを指定すると、指定したURIに関する情報のみを表示できる。

$ python nxtool.py --elastic-source --filter 'uri=/WebGoat/attack' --stat
# IP #
119.243.164.45: 180

# URI #
/WebGoat/attack: 180

# ZONE #
BODY: 163
ARGS: 17

# SERVER #
133.242.6.157: 180

このようにしてelasticsearch内に格納したログ情報は、「kibana」などのelasticsearch向けツールで閲覧することも可能だ。kibanaはたとえばDockerを利用する場合、次のようにして起動できる。

# docker pull kibana
# docker run --name kibana --link elastic:elasticsearch  -p 5601:5601 -d kibana

kibanaコンテナを起動したら、Webブラウザから「http://<ホスト>:5601」にアクセスするとその管理画面が表示される(図5)。ここで「Index name or pattern」に「nxapi」を指定して「Create」をクリックするとログデータにアクセスできるようになる。

図5 kibanaの管理画面

また、NAXSIが出力するログのタイムスタンプのタイムゾーンがWebブラウザの言語設定と一致していない場合、明示的に設定を行わないと検索対象の期間を正しく設定できない。たとえばログのタイムスタンプがUTCで記録されていた場合、「Management」画面の「Advanced Settings」内にある「dateFormat:tz」でタイムゾーンを「UTC」に設定し、「Save」をクリックしよう(図6)。

図6 kibanaのタイムゾーン設定

kibanaの詳しい使い方については割愛するが、たとえば「Discover」画面で対象範囲を「Today」に設定し、表示するパラメータを指定することで図7のようにその内容を見やすく閲覧できる。

図7 kibanaでの監査ログ閲覧

さて、先のWAF Testing Frameworkの出力結果を見てみると、False Positivesとして検出されているのはすべてリクエスト中にURLがパラメータとして含まれるものだった(図8)。

図8 WAF Testing Frameworkで検出されたFalse Positivesの詳細

実際にkibanaでそのリクエストに対する監査ログを検索してみると、それらに該当するログを確認できる(図9)。

図9 誤検知されたログ

nxtoolではログに記録されたリクエストを指定し、そのリクエストをブロックしないよう設定する「ホワイトリスト」を生成する機能がある。この機能を利用し、これらリクエストに対するホワイトリストを作成してみよう。

nxtoolを使ってホワイトリストを作成するには、次のように実行する。

 python nxtool.py --elastic-source --filter '<対象とするログを絞り込むフィルタ>' --whitelist

このとき、フィルタを指定しないと監査ログに記録されたすべてのアクセスをブロックしないような設定が生成されてしまうので注意しよう。また、「--stats」オプションを使用してログを絞り込みながら条件を絞り込んで行けば良い。今回は先のkibanaでの検索結果を参考に、IDが「1100」、ゾーンが「ARGS」、uriが「/WebGoat/attack」であるという条件で絞り込んだ。

$ python nxtool.py --elastic-source --filter 'id=1100,zone=ARGS,uri=/WebGoat/attack' --stats
# IP #
119.243.164.45: 8

# URI #
/WebGoat/attack: 8

# ZONE #
ARGS: 8

# SERVER #
133.242.6.157: 8

このフィルタを指定し、「--whitelist」オプション付きでnxtoolを実行すると、次のようにホワイトリスト設定が出力される。

$ python nxtool.py --elastic-source --filter 'id=1100,zone=ARGS,uri=/WebGoat/attack' --whitelist
[+] Generating Google analytics rules
[+] Generating Image 1002 rules
[+] Generating array-like variable name rules
[+] Generating cookies rules
[+] Generating var + zone + url rules
[+] Generating url rules
[+] Generating var + zone rules
[+] Generating zone rules
[+] Generating site rules

Generated whitelists:
        BasicRule wl:1100 "mz:ARGS" "msg:Site-wide id+zone if it matches http:// scheme";

ここで、「Generated whitelists:」以下に表示されるのが設定ファイルに記入するホワイトリスト設定となる。今回は「/usr/local/nginx/conf/naxsi_local.rules」という設定ファイルを作成して上記のホワイトリストを追加した上で、nginxの設定ファイル(nginx.conf)に下記のようにこのファイルを読み込む設定を追加した。

location / {
    #root   html;
    #index  index.html index.htm;
    proxy_pass http://localhost:8080/;

    SecRulesEnabled;
    DeniedUrl /50x.html;
    error_log logs/naxsi.log;

    CheckRule "$SQL >= 8" BLOCK;
    CheckRule "$RFI >= 8" BLOCK;
    CheckRule "$TRAVERSAL >= 4" BLOCK;
    CheckRule "$EVADE >= 4" BLOCK;
    CheckRule "$XSS >= 8" BLOCK;
    include /usr/local/nginx/conf/naxsi_local.rules;
}

この設定は、上記のようにlocationディレクティブ内に記述する必要がある点に注意しよう。

この設定を適用した状態で、再度WAF Testing Frameworkによるテストを実行した結果が図10だ。ホワイトリストの追加によってFalse Positivesの数は0になっているが、その影響でFalse Negativesの割合も大きく増えていることが確認できる。

図10 ホワイトリスト追加後のWAF Testing Frameworkによるテスト結果

WAFの導入時には環境に応じた設定のカスタマイズが重要

今回はngix向けのWAFを紹介したが、ModSecurtyとNAXSIのどちらを利用する場合でも、設定のカスタマイズが重要となる。また、注意したいのが実運用環境でのテストは必須だということだ。

前編でも述べたが、今回使用したWAF Testing FrameworkはあくまでWebGoatというアプリケーションに特化したものであり、汎用のセキュリティチェックツールではない。そのため、このツールで問題とされたリクエストでも実際には影響はなかったり、逆に問題ないとされたものが実際には脆弱性に繋がる可能性があるリクエストだったりする可能性もある。

WAFは適切に利用すればセキュリティの向上が期待できるが、ただ導入しただけでは問題を引き起こす可能性もある。その点に注意して活用して欲しい。