目次
NGINX mod_security
NGINXを利用して、Proxy/Signature型のWAFを構築する。
構成
今回のWAFの構成は以下の通り
+---------+ | +---+ | +-----+ | +--+ +--|LB(NGINX)|--+--|IPS|--+--|httpd|--+--|DB| +------+ | +---------+ | +---+ | +-----+ | +--+ |Router|--+ | | | +------+ | +---------+ | +---+ | +-----+ | +--+ +--|LB(NGINX)|--+--|IPS|--+--|httpd|--+--|DB| +---------+ | +---+ | +-----+ | +--+ | | +---+ +--|WAF| | +---+ | | +---+ +--|WAF| | +---+
通信は以下のように流れる予定
- 外部node → LB (TCP terminate) : LBはReverse Proxy/Cacheなので、HTTP通信は全て終端する
- LB → WAF (TCP terminate) : WAFはProxy-WAFなので、HTTP通信を全て終端する
- この時、LBは、必要な情報をHTTP Headerに付加して、WAFに送る
- WAF → LB (TCP terminate) : WAFは、通信内容を検査し、問題がなければLBに返送する。送り先は、LB内Dispatcher
- LB → httpd : LBは、(2)で付加された情報をもとに、接続先Web Server(httpd)を決定し、リクエストをIPS経由で送る
- IPSは、Router modeなので、検査の結果問題がなければ、通常通りトラフィックをForwardingする。
- HTTP/HTTPs通信に限っては、IPSは不要とも言えるので、構成上再検討の余地はある。
- httpd → LB(dispatcher) : LBがproxyモードなので、返答の送付先は自動的にdispatcherになる
- LB(dispatcher) → WAF : WAFがproxyモードなので、返答の送付先は自動的にWAFになる
- WAF → LB : LBがproxyモードなので、返答の送付先は自動的にWAFになる
- 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を利用する。
- FreeBSD ports collectionをシステムに展開する(略)
cd /usr/ports/www/nginx-devel
make configure
- Checkを外す : MAIL, MAIL_SSL, STREAM, STREAM_SSL
- Checkを入れる ; HTTP_GEOIP, LUA, MODSECURITY3
make
make install
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.h
に NGX_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の設定
本節で、NGINXおよびmod_securityの設定を記載し、WAFとして動作させる。
まずは簡単な試験
- NGINXの動作確認
- 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"; } } }
- nginxを起動する
- /etc/rc.conf.localに
nginx_enable=“YES”
を追加 service nginx start
を実行- Errorが出ていないことを確認する
- 正しく返答が返ってくることを確認
curl -D - http://localhost:8085
- これで、200 OKが返って来れば良い
- Proxyを作成する
- 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; } } ....
- 正しく返答が返ってくることを確認
service nginx reload
- Errorが出ないことを確認する
curl -D - http://localhost
- これで、200 OKが返って来れば良い
- mod_security3の動作確認
- /usr/local/etc/modsecurity以下で以下の作業を行う
cat modsecurity.conf.sample | sed 's/SecRuleEngine DetectionOnly/SecRuleEngine On'/ > modsecurity.conf
- 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"
- 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
- 通常の通信は通ることを確認する
curl -D - http://localhost
- これで、200 OKが返って来れば良い
- mod_securityがリクエストをDropしたことを確認する
- これで、403 Forbiddenが返って来れば良い
- signatureのOn/Offの制御
- 使用しないSignatureをOffにする方法は2つ。Ruleを読み込む前にOffにする方法とRuleを読んだ後にOffにする方法
- 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が返って来れば良い
- 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
- まずは、MonitorMode WAFを構築する
- この設定は、/usr/local/etc/modsecurity/modsecurity.conf.sampleから作成する。
- modsecurity.conf.sampleをMM-Base.confとしてコピーする
- 下記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/
- MonitorMode WAF設定を作成する
- /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
- 以下、MM-(pre|post)Load.confを作成する
- 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"
- 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
- 次にDropMode WAFを構築する。
- 基本は、MonitorMode WAFの構築と同様に、ファイル名のMMをDMに置き換えて作業する
- 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と管理構造を同じようにすることで、考え方を調整しなくて済むように考えている。
- local signatureの作成
mkdir /usr/local/waf/local-modsecurity-sigs
mkdir /usr/local/waf/local-modsecurity-sigs/rules
- 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.
- 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に接続できる端末で以下を実行する。
- 問題のない通信
$ 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 /
- 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>
- 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を書き換える方法はあるか?
今後確認しておくこと
- Project Honey Pot HTTP Blacklist
- GeoIP Database
- 国別の無制限Block(
Block Countries
on crs-setup.conf) - DoS防御(
Anti-Automation / DoS Protection
on crs-setup.conf) - UTF-8チェック(
Check UTF-8 encoding
on crs-setup.conf)- 一般にUTF-8の処理はむしろトラブルを生みやすいので、いつか試験しなければならない