転載・引用について

ユーザ用ツール

サイト用ツール


サイドバー

Site Contents Index

転載・引用について

RSS

networkapp:waf:mod-security

NGINX mod_security

NGINXを利用して、Proxy/Signature型のWAFを構築する。

構成

今回のWAFの構成は以下の通り

              +---------+  |  +---+  |  +-----+  |  +--+
           +--|LB(NGINX)|--+--|IPS|--+--|httpd|--+--|DB|
 +------+  |  +---------+  |  +---+  |  +-----+  |  +--+
 |Router|--+               |         |           |
 +------+  |  +---------+  |  +---+  |  +-----+  |  +--+
           +--|LB(NGINX)|--+--|IPS|--+--|httpd|--+--|DB|
              +---------+  |  +---+  |  +-----+  |  +--+
                           |
                           |  +---+
                           +--|WAF|
                           |  +---+
                           |
                           |  +---+
                           +--|WAF|
                           |  +---+

通信は以下のように流れる予定

  1. 外部node → LB (TCP terminate) : LBはReverse Proxy/Cacheなので、HTTP通信は全て終端する
  2. LB → WAF (TCP terminate) : WAFはProxy-WAFなので、HTTP通信を全て終端する
    • この時、LBは、必要な情報をHTTP Headerに付加して、WAFに送る
  3. WAF → LB (TCP terminate) : WAFは、通信内容を検査し、問題がなければLBに返送する。送り先は、LB内Dispatcher
  4. LB → httpd : LBは、(2)で付加された情報をもとに、接続先Web Server(httpd)を決定し、リクエストをIPS経由で送る
    • IPSは、Router modeなので、検査の結果問題がなければ、通常通りトラフィックをForwardingする。
    • HTTP/HTTPs通信に限っては、IPSは不要とも言えるので、構成上再検討の余地はある。
  5. httpd → LB(dispatcher) : LBがproxyモードなので、返答の送付先は自動的にdispatcherになる
  6. LB(dispatcher) → WAF : WAFがproxyモードなので、返答の送付先は自動的にWAFになる
  7. WAF → LB : LBがproxyモードなので、返答の送付先は自動的にWAFになる
  8. LB → 外部node

Install

例によってFreeBSDのServerを構築する。

諸元

VMの諸元は以下の通り

CPU vCPU x2
Memory 4G
Disk 0 20G Boot disk
NIC 0 Management I/F
NIC 1 WAF I/F
OS FreeBSD 11.2-p4 (2018/10/19)

OSのInstall

通常通り、minimum installを行う

WAFは、今回はProxy Serverと同じ扱いになるので、/etc/sysctl.confに以下を追記する。

# IP Tuning
net.inet.ip.portrange.randomized=0
net.inet.ip.portrange.first=1024
net.inet.ip.portrange.last=65535

# ICMP Tuning
net.inet.icmp.icmplim=3000

# TCP Tuning
net.inet.tcp.msl=1000
net.inet.tcp.finwait2_timeout=3000
net.inet.tcp.nolocaltimewait=1
net.inet.tcp.fast_finwait2_recycle=1
net.inet.tcp.syncookies=1
net.inet.tcp.recvspace=8192
# for XenServer VM
net.inet.tcp.tso=0

# HTTP tunning
kern.ipc.nmbclusters=262144     # default=25600
kern.ipc.nmbjumbop=128000       # default=12800
kern.ipc.somaxconn=32768
kern.ipc.maxsockets=204800
kern.ipc.maxsockbuf=20480000
kern.maxfiles=204800
kern.maxfilesperproc=200000

### ARP cache timeout
net.link.ether.inet.max_age=97

セマフォの拡張はboot時に行う必要があるので、/boot/loader.confに記述

/boot/loader.conf
### for WAF
kern.ipc.semmsl=340		# 340 : max # of semaphores per id
kern.ipc.semmns=128000		# 340 : # of semaphores in system
kern.ipc.semopm=100		# 100 : max # of operations per semop call
kern.ipc.semmni=512		# 50  : # of semaphore identifiers

kern.ipc.semaem=16384		# 16384 : adjust on exit max value
kern.ipc.semvmx=32767		# 32767 : semaphore maximum value
kern.ipc.semusz=632		# 632   : size in bytes of undo structure
kern.ipc.semume=50		# 50    : max # of undo entries per process
kern.ipc.semmnu=150		# 150   : # of undo structures in system

ModSecurityの処理は高速であることが望ましいので、modsecurity用のFilesystemをMemory Filesystemにする

  • /var/modsecurityを作成
  • /etc/fstabに以下の行を追加する
    • fstab
      tmpfs           /var/modsecurity tmpfs  rw,size=1G      0 0
  • mount -aを実行

WAFのInstall

Installには複数の手法があるが、今回は、構築・運用の簡便のため、FreeBSD Portsを利用することにする。

真面目にportsからCompileすると、大量のPackageをCompileする羽目になるので、一部binary packageでInstallする

# pkg install git GeoIP luajit mod_security3

GeoIP関連の設定

  • GeoIPが利用するアドレス情報をDownloadする
    • # /bin/sh /usr/local/bin/geoipupdate.sh
  • cronでDailyにGeoIP情報を更新する
    • # crontab -e
      
      0       6       *       *       *               /usr/local/bin/geoipupdate.sh > /dev/null

NGINXをInstallする

本体のNGINXは、Binary Packagesのnginx-develがmod_securityを含んでいないことや、WAFには不要な機能が大量に組み込まれているため、Portsを利用する。

  1. FreeBSD ports collectionをシステムに展開する(略)
  2. cd /usr/ports/www/nginx-devel
  3. make configure
    • Checkを外す : MAIL, MAIL_SSL, STREAM, STREAM_SSL
    • Checkを入れる ; HTTP_GEOIP, LUA, MODSECURITY3
  4. make
  5. make install
  6. pkg lock nginx-devel
    • pkg upgradeなどで自動更新されないようにする。

これで、NGINX+mod_security3のバイナリーがInstallされる。

WAFを作成する場合に、logをHDDに書き出す場合、HDDの書き込み速度が律速になり、NGINX自体が遅くなることがある。 しかし、WAFで、検査したRequestをできるだけ書き出したい場合もある。

NGINXは、標準でsyslogにlogを書き出すことができるが、その場合、log文字数は2048文字でハードコードされている。
( src/core/ngx_log.hNGX_MAX_ERROR_STR 2048 として記述されている。 )

syslogは、仕様としてはlog文字長に制限はないが、FreeBSDの syslogd の場合、2048文字までしか受け付けないようになっているが、remote log systemに fluentd を利用する場合、logの文字数の制限は事実上撤廃することが可能。 (参照: Fluentd)

この場合に、NGINXのlog文字列を増やし、かつ、Mod_Security3用の設定を毎回portsにて行うことは非常に面倒なので、portsを作成した。

以下、分かる人向け。

BaseにしたNGINXは、個人の好みでnginx-develにした。気に入らない人は、Makefileを書き換えればnginxにもできる。

ports/www/nginx-nginx-modsec3-logextend 及び ports/www/nginx-nginx-modsec3-logextend/filesを作成

Makefile
# Created by: seirios

PORTNAME=       nginx
PKGNAMESUFFIX=  -modsec3-logextend

MAINTAINER=     seirios@seirios.org
COMMENT=        Robust and small WWW server (mod_security3 and syslog buffer extended)

MASTERDIR=      ${.CURDIR}/../nginx-devel

CONFLICTS=      nginx-1.* \
                nginx-devel-1.* \
                nginx-full-1.* \
                nginx-naxsi-1.*

OPTIONS_DEFAULT=DSO FILE_AIO IPV6 THREADS WWW \
                HTTP HTTP_ADDITION HTTP_AUTH_REQ HTTP_CACHE HTTP_DAV HTTP_FLV \
                HTTP_GEOIP HTTP_REALIP HTTP_REWRITE HTTP_SSL HTTP_STATUS HTTPV2 \
                LUA MODSECURITY3

.include "${MASTERDIR}/Makefile"
files/extra-patch-src-core-ngx_log.h
# cat files/extra-patch-src-core-ngx_log.h
--- src/core/ngx_log.h.orig     2018-10-30 18:59:30.293977000 +0900
+++ src/core/ngx_log.h  2018-10-30 18:59:46.430785000 +0900
@@ -73,7 +73,7 @@
 };
 
 
-#define NGX_MAX_ERROR_STR   2048
+#define NGX_MAX_ERROR_STR   8192
 
 
 /*********************************/

あとは、通常通りmakeすれば良い。

見れば分かるが、基本 nginx-devel に依存し、参照するので、ngx_log.hが大幅に変更されない限り、継続して利用できるはずである。

OWASP CRSのInstall

mod_security用のsignature dataは /usr/local/wafに置くことにする

# mkdir /usr/local/waf
# cd /usr/local/waf
# git clone https://github.com/SpiderLabs/owasp-modsecurity-crs.git

2018/10/26現在、Releaseされているmod_securityは、3.0.2だが、OWASP CRSのHEADはv3.2/devであり、このCRSは、mod_security 3.1.0にて追加される機能を仮定している。
そのため、現在の段階では、以下の追加操作が必要になる。

# cd /usr/local/waf/owasp-modsecurity-crs
# git checkout v3.0/master

これにより、CRSがv3.0.2相当のものになる。

この作業を行わないと、CRSの読み込みの段階で複数のエラーが発生して大ハマりすることになる。

なお、現在利用しているBranchを確認するには、git branch -vを実行すること

また、OWASP CRSは比較的高頻度で更新されるので、ともあれ定期的にUpdateすることが必要

  • mkdir /usr/local/waf/bin
  • crsupdate.shを作成
    • bash crsupdate.sh
      #! /bin/sh
      cd /usr/local/waf/owasp-modsecurity-crs;
      /usr/local/bin/git pull
  • 試験
    • /bin/sh /usr/local/waf/bin/crsupdate.sh
    • 問題なく終了すればOK
      • 本来なら、Error Checkなどを行うべきだが、それは各自の宿題ということで。
  • cronに仕掛ける
    • 10      6       *       *       *               /bin/sh /usr/local/waf/bin/crsupdate.sh > /dev/null

全体環境の整備

今回修正が必要になるのは、LB部分。

処理の流れ

今回の構成の場合。LBには、以下の機能が必要。

  • 外部からの通信を受け付ける。以下本節内では、RLB (Receive LB)とする
  • WAFからの返答を受け取って、Web ServerにRequestを送る。 以下本節内では、DLB (dispatcher LB)とする

通信は以下のようになる。

node -> RLB(1) -> WAF -> DLB(1) -> Web Server -> DLB(2) -> WAF -> RLB(2) -> node
Request -------------------------->|        |-------------------------> Response

各点で必要となる処理

RLB(1) / DLB(1) / DLB(2) / RLB(2) のそれぞれの点で、正しく処理を行うための情報の加工が必要になる。

: RLB(1) : HTTP Headerにサイト内追加情報を付記し、SourceとDestの情報を保持する。WAFにSession単位でトラフィックを分散する
: DLB(1) : R+B(1)で追加されたHTTP Headerを除去し、バックエンドのWeb ServerにSession単位でTrafficを分散する
: DLB(2) : 特段の処理はない(戻りトラフィックであるため)
: RLB(2) : 特段の処理はない(戻りトラフィックであるため)

RLB(1) で追加する HTTP Header は以下の通り

proxy_set_header Host              $host;
proxy_set_header X-Forwarded-Host  $host;
proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP         $remote_addr;
proxy_set_header X-WAF-URL-FLAG    $scheme://$host:$server_port;

ここで重要なのは、X-WAF-URL-FLAGである。dispatcherは、このHeaderを参照して、配送先のWeb Serverを決定する。 なお、X-WAF-URL-FLAGは、内部処理に必要な情報であり、このHeaderを付記したリクエストによってNGINXが混乱させられることを避けるため、必ず、本Headerを一度削除してから追加すること

DLB(1)で削除する HTTP Header は以下の通り

  • X-WAF-URL-FLAG : これは、Site 内部でのみ必要な情報なので、Web Serverに引き渡す必要はない

これ以外のHeaderは、通常のHTTP Protocolの規定通りに処理すれば良い。

LBの設定例

今回の構成では、RLB/DLB共にLBとしてまとめている。 これは、今回投入するシステムの検査対象トラフィック量が少ないからであって、これを分離する構成も当然ながらとることが可能。

なお、分離構成する場合、以下のような2種類の構成を考えることも可能

Structure 1                                 Structure 2
         |  +---+  |  +---+  |  +---+         +---+  |  +---+  |  +---+  |         |
         +--|WAF|--+--|DLB|--+--|Web|       --|RLB|--+--|WAF|--+--|DLB|--+         |
         |  +---+  |  +---+  |  +---+         +---+  |  +---+  |  +---+  |         |
         |         |         |                  |    |         |         |         |
  +---+  |  +---+  |  +---+  |  +---+           |    |  +---+  |  +---+  |  +---+  |
--|RLB|--+--|WAF|--+--|DLB|--+--|WEB|           |    +--|WAF|--+--|DLB|--+--|IPS|--+
  +---+  |  +---+  |  +---+  |  +---+     +=====+    |  +---+  |  +---+  |  +---+  |
         |                   |            |     |    |                   |         |
  +---+  |       +---+       |  +----+    |   +---+  |                   |  +---+  |
--|RLB|--+-------|IPS|-------+--|Step|    | --|RLB|--+                   +--|IPS|--+
  +---+  |       +---+       |  +----+    |   +---+  |                   |  +---+  |
         |                   |            |                              |
         |       +---+       |            +==============================+ 
         +-------|IPS|-------+                                           |
         |       +---+       |              

Structure 1はHTTP通信に関してはIPSでの検査を行わないモデルであり、Structure 2は、今回作成するRLB/DLB一体構造を展開したものとなる。

これらの「展開された構成」は、トラフィックが多く、LBへの負荷が高くなってしまうような場合に採用する。

本構成において、RLBは比較的高負荷になることが予想される。これは、

  • WAFは暗号化されたRequestを検査することはできないので、RLBでSSLを展開する必要がある
  • WAFによる通信検査は比較的負荷が高いため、WAFに流入する通信は少なくなることが望ましい。そのため、RLBにおいて、画像などのCacheを行うことで負荷を軽減する策をとることが望ましい

からである。

以上より、LBにて行うべき処理は以下の通り

  • HTTP/HTTPs Requestの終端
  • (必要に応じて) SSLの展開
  • 静止画像など、変更が少ないデータのCache
  • 必要な情報を付加した上での、HTTP通信をWAFへ転送
  • WAFからの検査結果通信の終端とWebServerへのリクエスト

以下に、NGINX.confのサンプルを記載する

# NGINX.conf

http {
    include                   mime.types;
    server_tokens             off;
    ignore_invalid_headers    on;
    sendfile                  on;
    tcp_nopush                on;
    open_file_cache           max=100 inactive=300s;

    proxy_cache_key           $scheme$proxy_host$request_uri;
    proxy_temp_path           /var/tmp/nginx/temp 1 2;

    proxy_cache_valid         200 302 1h;
    proxy_cache_valid         any     1m;

ssl_ciphers ECDHE+RSAGCM:ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:!EXPORT:!DES:!3DES:!MD5:!DSS;
    ssl_prefer_server_ciphers on;
    ssl_protocols             TLSv1.2;
    ssl_session_cache         shared:SSLresumption:10m;
    ssl_session_timeout       10m;

##### For RLB

upstream WAF01 {
    ip_hash;
    server 192.0.2.11:80 max_fails=3 fail_timeout=300;
    server 192.0.2.12:80 max_fails=3 fail_timeout=300;

}

upstream WWW_EXAMPLE.COM {
    server 203.0.113.101:80 max_fails=3 fail_timeout=300;
    server 203.0.113.102:80 max_fails=3 fail_timeout=300;
}

upstream WWW_EXAMPLE.NET {
    server 203.0.113.111:80 max_fails=3 fail_timeout=300;
    server 203.0.113.112:80 max_fails=3 fail_timeout=300;
}

proxy_cache_path /var/tmp/nginx/cache/EXAMPLE_COM levels=1:2 keys_zone=EXAMPLE_COM:60m max_size=4000m inactive=8h;
proxy_cache_path /var/tmp/nginx/cache/EXAMPLE_NET levels=1:2 keys_zone=EXAMPLE_NET:60m max_size=4000m inactive=8h;

server { #RLB Configuration
    listen      198.51.100.1:80:
    server_name www.example.com;
    error_log   /var/log/nginx/example_com_error.log error;
    access_log  /var/log/nginx/example_com_access.log;

    location / {
        return 301 https://$host$request_uri;
    }
}

server { #RLB Configuration
    listen      198.51.100.1:443 ssl http2;
    server_name www.example.com;
    error_log   /var/log/nginx/example_com_ssl_error.log error;;
    access_log  /var/log/nginx/example_com_ssl_access.log;

    ssl_certificate     /usr/local/etc/Certs/www.example.com.cert;
    ssl_certificate_key /usr/local/etc/Certs/www.example.com.key;

    location / {
        proxy_pass        http://WAF01;
        proxy_cache       EXAMPLE.COM;
        proxy_cache_valid 200 302     1440m;
        proxy_cache_valid 404         1m;
        proxy_cache_valid 500 502 504 1m;
        proxy_set_header          Host $host;
        proxy_set_header          X-Forwarded-Host $host;
        proxy_set_header          X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header          X-Forwarded-Proto $scheme;
        proxy_set_header          X-Real-IP $remote_addr;
        proxy_set_header          X-WAF-URL-FLAG $scheme://$host:$server_port;
    }
}

....

##### For DLB
map $http_X_WAF_URL_FLAG $MYUPSTREAM {
    https://www.example.com:443 http://WWW_EXAMPLE_COM;		# ここには、upstreamを記載する
    https://www.example.net:443 http://WWW_EXAMPLE_NET;		# ここには、upstreamを記載する
}

server { #DLB
    listen 192.0.2.11:10080 accept_filter=httpready;
    server_name DLB1;
    error_log   /var/log/nginx/dlb01.err error;
    access_log  /var/log/nginx/dlb01.acc;

    location / {
        proxy_pass        $MYUPSTREAM;
        proxy_pass_header X-Accel-Buffering;
        proxy_set_header  Host $http_x_forwarded_host;
        proxy_set_header  X-WAF-URL-FLAG  "";
        proxy_set_header  X-Forwarded-Host $http_host;
        proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

WAFの設定

まずは簡単な試験

  1. NGINXの動作確認
    1. NGINXの設定(/usr/local/etc/nginx/nginx.confを作成
      • nginx.conf
        load_module /usr/local/libexec/nginx/ngx_http_geoip_module.so;
        load_module /usr/local/libexec/nginx/ndk_http_module.so;
        load_module /usr/local/libexec/nginx/ngx_http_lua_module.so;
        
        user www www;
        worker_processes 1;
        events {
          use kqueue;
          worker_connections 4096;
          accept_mutex on;
          accept_mutex_delay 500ms;
        }
        
        http {
          server {
            listen localhost:8085;
            location / {
              default_type text/plain;
              return 200 "Thank you for requesting ${request_uri}\n";
            }
          }
        }
    2. nginxを起動する
      • /etc/rc.conf.localにnginx_enable=“YES”を追加
      • service nginx startを実行
      • Errorが出ていないことを確認する
    3. 正しく返答が返ってくることを確認
  2. Proxyを作成する
    1. nginx.confに以下を追加する
      • nginx.conf
        ....
          server {
            listen 80;
        #    modsecurity on;
        #    modsecurity_rules_file /usr/local/etc/nginx/modsec/main.conf;
            location / {
              proxy_pass http://localhost:8085;
              proxy_set_header Host $host;
            }
          }
        ....
    2. 正しく返答が返ってくることを確認
      • service nginx reload
      • Errorが出ないことを確認する
      • curl -D - http://localhost
      • これで、200 OKが返って来れば良い
  3. mod_security3の動作確認
    1. /usr/local/etc/modsecurity以下で以下の作業を行う
      1. cat modsecurity.conf.sample | sed 's/SecRuleEngine DetectionOnly/SecRuleEngine On'/ > modsecurity.conf
      2. main.confを作成する
        • main.conf
          # Include the recommended configuration
          Include /usr/local/etc/nginx/modsec/modsecurity.conf
          # A test rule
          SecRule ARGS:testparam "@contains test" "id:1000,deny,log,status:403"
    2. nginx.confのlisten 80;以下のlocationでコメントアウトされているmod_security関連の設定を生かす
      • nginx.conf
        ....
          server {
            listen 80;
            modsecurity on;
            modsecurity_rules_file /usr/local/etc/nginx/modsec/main.conf;
            location / {
              proxy_pass http://localhost:8085;
              proxy_set_header Host $host;
            }
          }
        ....
      • nginxでConfigurationを読み込み直す service nginx reload
    3. 通常の通信は通ることを確認する
    4. mod_securityがリクエストをDropしたことを確認する
  4. signatureのOn/Offの制御
    • 使用しないSignatureをOffにする方法は2つ。Ruleを読み込む前にOffにする方法とRuleを読んだ後にOffにする方法
  5. Ruleを読み込む前にOffにする
    • # Include the recommended configuration
      Include /usr/local/etc/nginx/modsec/modsecurity.conf
      
      # Omit Rule
      ....
      # A test rule
      SecRule ARGS:testparam "@contains test" "id:1000,deny,log,status:403"
    • これで、200 OKが返って来れば良い
  6. Ruleを読み込んだ後にOffにする
    • # Include the recommended configuration
      Include /usr/local/etc/nginx/modsec/modsecurity.conf
      
      # A test rule
      SecRule ARGS:testparam "@contains test" "id:1000,deny,log,status:403"
      
      # Omit Rule
      ....
      
    • これで、200 OKが返って来れば良い

OWASP CRSを用いたWAFの構築

WAFを作成する場合、False Positive(偽陽性)つまり、過検知による「止める必要がない」「止めてはいけない」通信による障害を可能な限り最小限に留めなければならない。そのため、以下のような手段を取ることが多い

  • Detect Only モード(以下Monitorモード)でWAFを投入
  • logに現れる各種通信を確認
  • 不要なSignatureを外す、またはSignatureのscoreを落とす
  • Dropモードに移行

したがって、WAFを構築する場合、最低でも、MonitorModeWAFの設定とDropModeWAFの設定が必要になる。

加えて、検査するサイトにおいて、調整するSignatureが異なることになるので、その部分の差異を取り込めるようにする設定を考える必要がある。

ここでは、Siteごとの調整は、WAFを分離することで行うこととする(for ex. Dokuwiki用WAFとWP用WAFを分離するなど)。

設定ファイルのディレクトリ構成

以下に、本設定で採用したDirectory構成、ファイル構成を記載する。

/-+
  +-- ...
  +- var -+- ...
  |       +- modsecurity                                               : mod_securityのWork Directory
  |
  +- usr/local -+- ...
                +- waf -+- bin -+- crsupdate.sh                        : CRS update用shell script
                |       +- local-modsecurity-sigs -+- local-setup.conf : local configuration
                |       |                          +- rules/*          : local signatures
                |       |
                |       +- owasp-modsecurity-crs -+- ...
                |                                 +- crs-setup.conf    : OWASP CRS configuration
                |                                 +- rules/*           : OWASP CRS signatures
                +- etc -+- modsecurity/*                               : FreeBSD portsによって作成される
                        +- nginx -+- ...                               : FreeBSD portsによって作成される
                                  +- modsec -+- MM-Base.conf           : MonitorMode用基本設定
                                  |          +- MM0.conf               : MonitorMode WAF#0 設定
                                  |          +- MM0-preLoad.conf       : MonitorMode WAF#0用rule読み込み前設定
                                  |          +- MM0-postLoad.conf      : MonitorMode WAF#0用rule読み込み後設定
                                  |          +- DM-Base.conf           : DropMode用基本設定
                                  |          +- DM0.conf               : DropMode WAF#0 設定
                                  |          +- DM0-preLoad.conf       : DropMode WAF#0用rule読み込み前設定
                                  |          +- DM0-postLoad.conf      : DropMode WAF$0用rule読み込み後設定
                                  +- conf.http                         : http configuration
                                  +- conf.http.d -+- ...               : server/location configuration

MonitorModeWAF

  1. まずは、MonitorMode WAFを構築する
    • この設定は、/usr/local/etc/modsecurity/modsecurity.conf.sampleから作成する。
      1. modsecurity.conf.sampleをMM-Base.confとしてコピーする
      2. 下記patchを当てる
      • # cp /usr/local/etc/modsecurity/modsecurity.conf.sample /usr/local/etc/nginx/modsec/MM-Base.conf
        # patch < MM-Base.conf.diff
      • MonitorModeBase.conf.diff
        # diff -u ../../modsecurity/modsecurity.conf.sample MM-Base.conf
        --- ../../modsecurity/modsecurity.conf.sample   2018-11-04 09:12:19.000000000 +0900
        +++ MM-Base.conf        2018-11-14 16:54:42.293424000 +0900
        @@ -35,7 +35,8 @@
         # to the size of data, with files excluded. You want to keep that value as
         # low as practical.
         #
        -SecRequestBodyLimit 13107200
        +#SecRequestBodyLimit 13107200
        +SecRequestBodyLimit 104857600
         SecRequestBodyNoFilesLimit 131072
         
         # What do do if the request body size is above our configured limit.
        @@ -103,7 +104,8 @@
         # Do keep in mind that enabling this directive does increases both
         # memory consumption and response latency.
         #
        -SecResponseBodyAccess On
        +#SecResponseBodyAccess On
        +SecResponseBodyAccess Off
         
         # Which response MIME types do you want to inspect? You should adjust the
         # configuration below to catch documents but avoid static files
        @@ -129,13 +131,15 @@
         # This default setting is chosen due to all systems have /tmp available however,
         # this is less than ideal. It is recommended that you specify a location that's private.
         #
        -SecTmpDir /tmp/
        +#SecTmpDir /tmp/
        +SecTmpDir /var/modsecurity
         
         # The location where ModSecurity will keep its persistent data.  This default setting
         # is chosen due to all systems have /tmp available however, it
         # too should be updated to a place that other users can't access.
         #
        -SecDataDir /tmp/
        +#SecDataDir /tmp/
        +SecDataDir /var/modsecurity
         
         
         # -- File uploads handling configuration -------------------------------------
        @@ -174,7 +178,8 @@
         # trigger a server error (determined by a 5xx or 4xx, excluding 404,
         # level response status codes).
         #
        -SecAuditEngine RelevantOnly
        +#SecAuditEngine RelevantOnly
        +SecAuditEngine off
         SecAuditLogRelevantStatus "^(?:5|4(?!04))"
         
         # Log everything we know about a transaction.
        @@ -184,7 +189,8 @@
         # assumes that you will use the audit log only ocassionally.
         #
         SecAuditLogType Serial
        -SecAuditLog /var/log/modsec_audit.log
        +#SecAuditLog /var/log/modsec_audit.log
        +SecAuditLog /var/log/nginx/modsec_audit.log
         
         # Specify the path for concurrent audit logging.
         #SecAuditLogStorageDir /opt/modsecurity/var/audit/
  2. MonitorMode WAF設定を作成する
    1. /usr/local/etc/nginx/modsec/MM.confを作成
    • MM.conf
      #
      # mod_security3 Monitor Mode WAF Configuration.
      #
      
      # ModSecurity configuration for DetectMode.
      Include /usr/local/etc/nginx/modsec/MM-Base.conf
      
      # Load signature configuration
      Include /usr/local/waf/owasp-modsecurity-crs/crs-setup.conf
      Include /usr/local/waf/local-modsecurity-sigs/local-setup.conf
      
      # Preload of omitting signature
      Include /usr/local/etc/nginx/modsec/MM-preLoad.conf
      
      # Load Signature Rules.
      Include /usr/local/waf/owasp-modsecurity-crs/rules/*.conf
      Include /usr/local/waf/local-modsecurity-sigs/rules/*.conf
      
      # Postload of omitting signature
      Include /usr/local/etc/nginx/modsec/MM-postLoad.conf
  3. 以下、MM-(pre|post)Load.confを作成する
    1. MM-preLoad.conf
      #
      # ModSecurity preload configuration.
      #
      # id: 1000 - 1999 : for OWASP CRS
      # id: 2000 - 2999 : Reserve
      # id: 3000 - 4999 : for local Sigs
      
      #SecAction "id:'1000',phase:1,t:none,pass,nolog,ctl:ruleRemoveById=900000"
    2. MM-postLoad.conf
      #
      # ModSecurity postload configuration.
      #
      # id: 5000 - 5999 : for OWASP CRS
      # id: 6000 - 6999 : Reserve
      # id: 7000 - 8999 : for local Sigs
      
      #SecRuleRemoveById 900000

これで、MonitorMode WAFの設定ファイルが完成。

DropModeWAF

  1. 次にDropMode WAFを構築する。
    1. 基本は、MonitorMode WAFの構築と同様に、ファイル名のMMをDMに置き換えて作業する
    2. DM-Base.confの以下の行を書き換える
      • DM-Base.patch
         diff -u MM-Base.conf DM-Base.conf
        --- MM-Base.conf        2018-10-26 14:40:57.052583000 +0900
        +++ DM-Base.conf        2018-10-25 16:52:54.400870000 +0900
        @@ -4,7 +4,7 @@
         # only to start with, because that minimises the chances of post-installation
         # disruption.
         #
        -SecRuleEngine DetectionOnly
        +SecRuleEngine On
         
         
         # -- Request body handling ---------------------------------------------------

これで、DropMode WAFの構築も終了。

なお、実際には、MonitorModeは検出のみだから全Signatureを利用していても性能が劣化する程度で済むことが多いが、DropModeの場合は、preLoadやPostloadの設定で、不要なSignatureをOffにする、もしくは重みつけを0にする必要があることに注意。

preLoadやPostLoadを作成してあるのはそのため。

OWASP CRSの設定

OWASP CRSの設定は、/usr/local/waf/owasp-modsecurity-crs/crs-setup.confによって行う。実際には、このファイルをModSecurityの設定ファイルから読み込んでいる。

  • cp /usr/local/waf/owasp-modsecurity-crs/crs-setup.conf.example /usr/local/waf/owasp-modsecurity-crs/crs-setup.conf
  • patch < crs-setup.conf.diff
    • crs-setup.conf.diff
      --- crs-setup.conf.example      2018-10-26 14:05:01.258754000 +0900
      +++ crs-setup.conf      2018-10-26 16:40:14.631386000 +0900
      @@ -563,6 +563,7 @@
       # Uncomment this rule to use this feature:
       #
       #SecGeoLookupDB util/geo-location/GeoIP.dat
      +SecGeoLookupDB /usr/local/share/GeoIP/GeoIP.dat
       
       
       #
      @@ -611,15 +612,15 @@
       #
       # Uncomment this rule to use this feature:
       #
      -#SecAction \
      -# "id:900700,\
      -#  phase:1,\
      -#  nolog,\
      -#  pass,\
      -#  t:none,\
      -#  setvar:'tx.dos_burst_time_slice=60',\
      -#  setvar:'tx.dos_counter_threshold=100',\
      -#  setvar:'tx.dos_block_timeout=600'"
      +SecAction \
      + "id:900700,\
      +  phase:1,\
      +  nolog,\
      +  pass,\
      +  t:none,\
      +  setvar:'tx.dos_burst_time_slice=60',\
      +  setvar:'tx.dos_counter_threshold=100',\
      +  setvar:'tx.dos_block_timeout=600'"
       
       
       #
      @@ -631,13 +632,13 @@
       #
       # Uncomment this rule to use this feature:
       #
      -#SecAction \
      -# "id:900950,\
      -#  phase:1,\
      -#  nolog,\
      -#  pass,\
      -#  t:none,\
      -#  setvar:tx.crs_validate_utf8_encoding=1"
      +SecAction \
      + "id:900950,\
      +  phase:1,\
      +  nolog,\
      +  pass,\
      +  t:none,\
      +  setvar:tx.crs_validate_utf8_encoding=1"
       
       
       #
      @@ -670,24 +671,24 @@
       #
       # Uncomment this rule to use this feature:
       #
      -#SecAction \
      -# "id:900960,\
      -#  phase:1,\
      -#  nolog,\
      -#  pass,\
      -#  t:none,\
      -#  setvar:tx.do_reput_block=1"
      +SecAction \
      + "id:900960,\
      +  phase:1,\
      +  nolog,\
      +  pass,\
      +  t:none,\
      +  setvar:tx.do_reput_block=1"
       #
       # Uncomment this rule to change the blocking time:
       # Default: 300 (5 minutes)
       #
      -#SecAction \
      -# "id:900970,\
      -#  phase:1,\
      -#  nolog,\
      -#  pass,\
      -#  t:none,\
      -#  setvar:tx.reput_block_duration=300"
      +SecAction \
      + "id:900970,\
      +  phase:1,\
      +  nolog,\
      +  pass,\
      +  t:none,\
      +  setvar:tx.reput_block_duration=300"
       
       
       #

これで、設定は完了。なお、個人の趣味が入っているので、設定自体は見直してください。

local signatureの設定

local signatureは、ここでは、自作のSignatureを仮定しているが、要するに、自己管理の各種Signatureのことであるとする。

local signatureは、OWASP CRSと管理構造を同じようにすることで、考え方を調整しなくて済むように考えている。

  1. local signatureの作成
    1. mkdir /usr/local/waf/local-modsecurity-sigs
    2. mkdir /usr/local/waf/local-modsecurity-sigs/rules
    3. local-setup.confを作成
      • local-setup.conf
        # ------------------------------------------------------------------------
        # ModSecurity local ruleset configuration.
        # ------------------------------------------------------------------------
        
        # ID
        # 1000-1999: Preload for OWASP CRS
        # 2000-3999: Reserve
        # 4000-4999: Preload for local Sigs.
        # 5000-5999: Preload for OWASP CRS
        # 6000-7999: Reserve
        # 8000-8999: Preload for local Sigs.
        # 10000-10999: local signatures part 1
        # 11000-11999: local signatures part 2 (Reserved)
        # .....
        # 19000-19999: local signatures part 9 (Reserved)
        
        # -- [[ System Requirements ]] -------------------------------------------------
        #
        # LOCAL SIGNATURES requires ModSecurity version 3.0.0 or above.
        # We recommend to always use the newest ModSecurity version.
        #
        # The order of file inclusion in your webserver configuration should always be:
        # 1. modsecurity.conf
        # 2. local-setup.conf (this file)
        # 3. rules/*.conf (the LOCAL SIGNATURES rule files)
        #
        
        # -- [[ WARNINGS ]] -------------------------------------------------
        # This signatures are using with OWASP modsecurity CRS.
    4. rules/LocalSignature-10.confを作成
      • LocalSignature-10.conf
        #
        # mod_security3 Local signatures #1.
        #
        # id: 10000-10999
        
        #SecRule ARGS:testparams "@contains test" "id:10000,deny,log,status:403"

以上で、local signature関連の設定は終了

NGINXの設定

ここまで準備できたら、NGINXをWAFとして動作させるための設定を投入する。

nginx.conf
load_module /usr/local/libexec/nginx/ngx_http_geoip_module.so;
load_module /usr/local/libexec/nginx/ndk_http_module.so;        # need fo lua
load_module /usr/local/libexec/nginx/ngx_http_lua_module.so;

user www www;
worker_processes 1;
events {
  use kqueue;
  worker_connections 4096;
  accept_mutex on;
  accept_mutex_delay 500ms;
}

http {
  server {
    listen localhost:8085;
    location / {
      default_type text/plain;
      return 200 "Thank you for requesting ${request_uri}\n";
    }
  }

  server { # DropMode
    listen 80;
    modsecurity on;
    error_log /var/log/nginx/test.err info;
    location / {
      modsecurity_rules_file /usr/local/etc/nginx/modsec/DM.conf;
      proxy_pass http://localhost:8085;
      proxy_set_header Host $host;
    }
  }
  server { # MonitorMode
    listen 81;
    modsecurity on;
    error_log /var/log/nginx/test.err info;
    location / {
      modsecurity_rules_file /usr/local/etc/nginx/modsec/MM.conf;
      proxy_pass http://localhost:8085;
      proxy_set_header Host $host;
    }
  }

}

動作検証

ここまでの通り設定してきたら、問題なく MonitorMode、DropModeのWAFが動作していることになる。

以下動作検証。

Drop Modeの検証

WAFに接続できる端末で以下を実行する。

  1. 問題のない通信
    • $ curl -D - http://waf:80
      HTTP/1.1 200 OK
      Server: nginx/1.15.5
      Date: Tue, 30 Oct 2018 06:02:23 GMT
      Content-Type: text/plain
      Content-Length: 27
      Connection: keep-alive
      
      Thank you for requesting /
  2. Dropすべき通信 1 (local signatures)
    • $ curl -D - http://waf:80/?testparams=test
      HTTP/1.1 403 Forbidden
      Server: nginx/1.15.5
      Date: Tue, 30 Oct 2018 06:02:39 GMT
      Content-Type: text/html
      Content-Length: 153
      Connection: keep-alive
      
      <html>
      <head><title>403 Forbidden</title></head>
      <body>
      <center><h1>403 Forbidden</h1></center>
      <hr><center>nginx/1.15.5</center>
      </body>
      </html>
  3. Dropすべき通信 2(OWASP CRS)
    • $ curl -D - http://waf:80/?union+select
      HTTP/1.1 403 Forbidden
      Server: nginx/1.15.5
      Date: Tue, 30 Oct 2018 06:09:13 GMT
      Content-Type: text/html
      Content-Length: 153
      Connection: keep-alive
      
      <html>
      <head><title>403 Forbidden</title></head>
      <body>
      <center><h1>403 Forbidden</h1></center>
      <hr><center>nginx/1.15.5</center>
      </body>
      </html>

Drop modeなら、これだけで判断できるが、一応 log を確認しておくこと

# Local signature 検出分
xxxx/xx/xx xx:xx:xx [info] 3985#100131: *108 ModSecurity: Access denied with code %d (phase 1). Matched "Operator `Contains' with parameter `test' against variable `ARGS:testparams' (Value: `test' ) [file "/usr/local/etc/nginx/modsec/DM.conf"] [line "115"] [id "10000"] [rev ""] [msg ""] [data ""] [severity "0"] [ver ""] [maturity "0"] [accuracy "0"] [hostname "192.0.2.254"] [uri "/"] [unique_id "154087989194.699201"] [ref "o0,4v17,4"], client: 192.0.2.254, server: , request: "GET /?testparams=test HTTP/1.1", host: "waf"

xxxx/xx/xx 15:11:31 [warn] 3985#100131: *108 [client 192.0.2.254] ModSecurity: Warning. Matched "Operator `Contains' with parameter `test' against variable `ARGS:testparams' (Value: `test' ) [file "/usr/local/etc/nginx/modsec/DM.conf"] [line "115"] [id "10000"] [rev ""] [msg ""] [data ""] [severity "0"] [ver ""] [maturity "0"] [accuracy "0"] [hostname "192.0.2.254"] [uri "/"] [unique_id "154087989194.699201"] [ref "o0,4v17,4"], client: 192.0.2.254, server: , request: "GET /?testparams=test HTTP/1.1", host: "waf"

# OWASP CRS検出分(union+select)
xxxx/xx/xx xx:xx:xx [info] 3985#100131: *109 ModSecurity: Warning. Matched "Operator `Rx' with parameter `(?i:(?:\s*?(?:exec|execute).*?(?:\W)xp_cmdshell)|(?:[\"'`]\s*?!\s*?[\"'`\w])|(?:from\W+information_schema\W)|(?:(?:(?:current_)?user|database|schema|connection_id)\s*?\([^\)]*?)|(?:[\"'`];?\s*?(?:sele (185 characters omitted)' against variable `ARGS_NAMES:union select' (Value: `union select' ) [file "/usr/local/waf/owasp-modsecurity-crs/rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf"] [line "150"] [id "942190"] [rev "2"] [msg "Detects MSSQL code execution and information gathering attempts"] [data "Matched Data: union select found within ARGS_NAMES:union select: union select"] [severity "2"] [ver "OWASP_CRS/3.0.0"] [maturity "9"] [accuracy "8"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-sqli"] [tag "OWASP_CRS/WEB_ATTACK/SQL_INJECTION"] [tag "WASCTC/WASC-19"] [tag "OWASP_TOP_10/A1"] [tag "OWASP_AppSensor/CIE1"] [tag "PCI/6.5.2"] [hostname "192.0.2.254"] [uri "/"] [unique_id "154087989855.740312"] [ref "o0,12v6,12t:urlDecodeUni"], client: 192.0.2.254, server: , request: "GET /?union+select HTTP/1.1", host: "waf"

xxxx/xx/xx xx:xx:xx [info] 3985#100131: *109 ModSecurity: Access denied with code %d (phase 2). Matched "Operator `Ge' with parameter `5' against variable `TX:ANOMALY_SCORE' (Value: `5' ) [file "/usr/local/waf/owasp-modsecurity-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "36"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [data ""] [severity "2"] [ver ""] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "192.0.2.254"] [uri "/"] [unique_id "154087989855.740312"] [ref ""], client: 192.0.2.254, server: , request: "GET /?union+select HTTP/1.1", host: "waf"

xxxx/xx/xx xx:xx:xx [warn] 3985#100131: *109 [client 192.0.2.254] ModSecurity: Warning. Matched "Operator `Ge' with parameter `5' against variable `TX:ANOMALY_SCORE' (Value: `5' ) [file "/usr/local/waf/owasp-modsecurity-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "36"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [data ""] [severity "2"] [ver ""] [maturity "0"] [accuracy "0"] [hostname "192.0.2.254"] [uri "/"] [unique_id "154087989855.740312"] [ref ""], client: 192.0.2.254, server: , request: "GET /?union+select HTTP/1.1", host: "waf"

xxxx/xx/xx xx:xx:xx [info] 3985#100131: *109 ModSecurity: Warning. Matched "Operator `Ge' with parameter `5' against variable `TX:INBOUND_ANOMALY_SCORE' (Value: `5' ) [file "/usr/local/waf/owasp-modsecurity-crs/rules/RESPONSE-980-CORRELATION.conf"] [line "61"] [id "980130"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Inbound Score: 5 - SQLI=5,XSS=0,RFI=0,LFI=0,RCE=0,PHPI=0,HTTP=0,SESS=0): Detects MSSQL code execution and information gathering attempts'"] [data ""] [severity "0"] [ver ""] [maturity "0"] [accuracy "0"] [tag "event-correlation"] [hostname "192.0.2.254"] [uri "/"] [unique_id "154087989855.740312"] [ref ""] while logging request, client: 192.0.2.254, server: , request: "GET /?union+select HTTP/1.1", host: "waf"

Monitor Modeの検証

Monitor Modeでの動作確認は、HTTPレベルでのやり取りを見ても判断はできない。 そのため、上記と同じ試験を行った上で、logを見て判断する。

$ curl -D - http://waf:81
$ curl -D - http://waf:81/?testparams=test
$ curl -D - http://waf:81/?union+select

以下logの例

# Local Signature検出分
xxxx/xx/xx xx:xx:xx [info] 95104#100127: *113 ModSecurity: Warning. Matched "Operator `Contains' with parameter `test' against variable `ARGS:testparams' (Value: `test' ) [file "/usr/local/etc/nginx/modsec/MM.conf"] [line "115"] [id "10000"] [rev ""] [msg ""] [data ""] [severity "0"] [ver ""] [maturity "0"] [accuracy "0"] [hostname "192.0.2.254"] [uri "/"] [unique_id "154088069495.032656"] [ref "o0,4v17,4"], client: 192.0.2.254, server: , request: "GET /?testparams=test HTTP/1.1", host: "waf:81"

# OWASP CRS検出分(union+select)
xxxx/xx/xx xx:xx:xx [info] 95104#100127: *116 ModSecurity: Warning. Matched "Operator `Rx' with parameter `(?i:(?:\s*?(?:exec|execute).*?(?:\W)xp_cmdshell)|(?:[\"'`]\s*?!\s*?[\"'`\w])|(?:from\W+information_schema\W)|(?:(?:(?:current_)?user|database|schema|connection_id)\s*?\([^\)]*?)|(?:[\"'`];?\s*?(?:sele (185 characters omitted)' against variable `ARGS_NAMES:union select' (Value: `union select' ) [file "/usr/local/waf/owasp-modsecurity-crs/rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf"] [line "150"] [id "942190"] [rev "2"] [msg "Detects MSSQL code execution and information gathering attempts"] [data "Matched Data: union select found within ARGS_NAMES:union select: union select"] [severity "2"] [ver "OWASP_CRS/3.0.0"] [maturity "9"] [accuracy "8"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-sqli"] [tag "OWASP_CRS/WEB_ATTACK/SQL_INJECTION"] [tag "WASCTC/WASC-19"] [tag "OWASP_TOP_10/A1"] [tag "OWASP_AppSensor/CIE1"] [tag "PCI/6.5.2"] [hostname "192.0.2.254"] [uri "/"] [unique_id "15408806983.603717"] [ref "o0,12v6,12t:urlDecodeUni"], client: 192.0.2.254, server: , request: "GET /?union+select HTTP/1.1", host: "waf:81"

xxxx/xx/xx xx:xx:xx [info] 95104#100127: *116 ModSecurity: Warning. Matched "Operator `Ge' with parameter `5' against variable `TX:ANOMALY_SCORE' (Value: `5' ) [file "/usr/local/waf/owasp-modsecurity-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "36"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [data ""] [severity "2"] [ver ""] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "192.0.2.254"] [uri "/"] [unique_id "15408806983.603717"] [ref ""], client: 192.0.2.254, server: , request: "GET /?union+select HTTP/1.1", host: "waf:81"

xxxx/xx/xx xx:xx:xx [info] 95104#100127: *116 ModSecurity: Warning. Matched "Operator `Ge' with parameter `5' against variable `TX:INBOUND_ANOMALY_SCORE' (Value: `5' ) [file "/usr/local/waf/owasp-modsecurity-crs/rules/RESPONSE-980-CORRELATION.conf"] [line "61"] [id "980130"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Inbound Score: 5 - SQLI=5,XSS=0,RFI=0,LFI=0,RCE=0,PHPI=0,HTTP=0,SESS=0): Detects MSSQL code execution and information gathering attempts'"] [data ""] [severity "0"] [ver ""] [maturity "0"] [accuracy "0"] [tag "event-correlation"] [hostname "192.0.2.254"] [uri "/"] [unique_id "15408806983.603717"] [ref ""] while logging request, client: 192.0.2.254, server: , request: "GET /?union+select HTTP/1.1", upstream: "http://127.0.0.1:8085/?union+select", host: "waf:81"

落ち穂拾い

Memo

  • *-Base.conf内の SecRuleEngine 設定を記述せず、location内に記述したらどうなるか?
  • server Directive内で*-Base.confを設定し、location Directive内でSecRuleEngineを書き換える方法はあるか?

今後確認しておくこと

Link集

このウェブサイトはクッキーを使用しています。 Webサイトを使用することで、あなたはあなたのコンピュータにクッキーを保存することに同意します。 また、あなたはあなたが私たちのプライバシーポリシーを読んで理解したことを認めます。 同意しない場合はウェブサイトを離れてください。クッキーに関する詳細情報
networkapp/waf/mod-security.txt · 最終更新: 2018/11/14 17:00 by seirios