在CentOS7 上 安裝Nginx + ModSecurity建立免費WAF

WAF是Web Application Firewall的縮寫

其原理是將已知的攻擊手法,用匹配方式偵測並阻擋

所以可以預想得到

規則會列很多條

也不一定全面

只需要熟悉漏洞原理的人

照樣可以繞過

但有總比沒有好

至少可以過濾掉大部分的script kiddie

除了ModSecurity之外

還有一個專門for nginx的WAF叫做naxsi

這個有機會也可以試用看看

不過這次先用ModSecurity

要把nginx跟ModSecurity結合

必須要自己compile nginx和ModSecurity

以下用我寫的shell script一步一步做講解

Step 1. 安裝compile所需套件

yum install -y git wget gcc gcc-c++ pcre-devel zlib-devel openssl openssl-devel httpd-devel libxml2-devel xz-devel python-devel libcurl-devel libxslt-devel gd gd-devel gmp gmp-devel perl-Tk-devel perl-ExtUtils-Embed.noarch GeoIP GeoIP-devel gperftools gperftools-devel
yum groupinstall -y 'Development Tools'

Step 2. 下載nginx和ModSecurity的source code 並解壓縮

wget https://www.modsecurity.org/tarball/2.9.1/modsecurity-2.9.1.tar.gz
wget http://nginx.org/download/nginx-1.10.3.tar.gz

tar zxvf modsecurity-2.9.1.tar.gz
tar zxvf nginx-1.10.3.tar.gz

Step 3. 用standalone方式Compile ModSecurity

cd modsecurity-2.9.1
./configure --enable-standalone-module --disable-mlogc
make
make install
cd ..

Step 4. Compile nginx with ModSecurity

cd nginx-1.10.3
sed -i "s/Server: nginx/Server: Hello/g" src/http/ngx_http_header_filter_module.c
./configure --prefix=/usr/share/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --http-client-body-temp-path=/var/lib/nginx/tmp/client_body --http-proxy-temp-path=/var/lib/nginx/tmp/proxy --http-fastcgi-temp-path=/var/lib/nginx/tmp/fastcgi --http-uwsgi-temp-path=/var/lib/nginx/tmp/uwsgi --http-scgi-temp-path=/var/lib/nginx/tmp/scgi --pid-path=/run/nginx.pid --lock-path=/run/lock/subsys/nginx --user=nginx --group=nginx --with-file-aio --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_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 --with-google_perftools_module --with-debug --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -m64 -mtune=generic' --add-module=../modsecurity-2.9.1/nginx/modsecurity

make
make install

第二行的sed指令,主要在修改http header裡的Server signature

預設會顯示Server:nginx,我把它改成Server:Hello

第三行的configure一大串的選項

我其實是參考Centos7裡用yum安裝的nginx的選項

用nginx -V可以看得到

並在最後面加上

--add-module=../modsecurity-2.9.1/nginx/modsecurity

Step 5. 加入nginx user和創建相關資料夾

adduser --no-create-home --user-group -s /sbin/nologin nginx
mkdir /var/lib/nginx
mkdir /var/lib/nginx/tmp
chown -R nginx:nginx /var/lib/nginx

mkdir /etc/nginx/conf.d
mkdir /etc/nginx/sites-available
mkdir /etc/nginx/sites-enabled

Step 6. 手動加入Service

/bin/cat <<EOM >/usr/lib/systemd/system/nginx.service
[Unit]
Description=The nginx HTTP and reverse proxy server
After=network.target remote-fs.target nss-lookup.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
# Nginx will fail to start if /run/nginx.pid already exists but has the wrong
# SELinux context. This might happen when running `nginx -t` from the cmdline.
# https://bugzilla.redhat.com/show_bug.cgi?id=1268621
ExecStartPre=/usr/bin/rm -f /run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx
ExecReload=/bin/kill -s HUP \$MAINPID
KillSignal=SIGQUIT
TimeoutStopSec=5
KillMode=process
PrivateTmp=true

[Install]
WantedBy=multi-user.target
EOM

systemctl daemon-reload

cd ..

這一步驟其實也是copy用yum安裝好的nginx.service

 

Step 7. 將ModSecurity設定整合進nginx

cp modsecurity-2.9.1/modsecurity.conf-recommended /etc/nginx/modsecurity.conf
cp modsecurity-2.9.1/unicode.mapping /etc/nginx
sed -ie 's/SecRuleEngine DetectionOnly/SecRuleEngine On/g' /etc/nginx/modsecurity.conf
sed -ie 's/SecPcreMatchLimit .*$/SecPcreMatchLimit 150000/g' /etc/nginx/modsecurity.conf
sed -ie 's/SecPcreMatchLimitRecursion .*$/SecPcreMatchLimitRecursion 150000/g' /etc/nginx/modsecurity.conf
sed -ie 's/SecAuditLogType Serial/SecAuditLogType Concurrent/g' /etc/nginx/modsecurity.conf
sed -ie "/^SecAuditLogType Concurrent$/aSecAuditLogStorageDir \/var\/log\/nginx" /etc/nginx/modsecurity.conf

 

 

Step 8. 安裝OWASP top 10 rules

git clone https://github.com/SpiderLabs/owasp-modsecurity-crs.git
mv owasp-modsecurity-crs /etc/nginx
cd /etc/nginx/owasp-modsecurity-crs
cp crs-setup.conf.example crs-setup.conf
sed -ie 's/SecDefaultAction "phase:1,log,auditlog,pass"/#SecDefaultAction "phase:1,log,auditlog,pass"/g' crs-setup.conf
sed -ie 's/SecDefaultAction "phase:2,log,auditlog,pass"/#SecDefaultAction "phase:2,log,auditlog,pass"/g' crs-setup.conf
sed -ie 's/#.*SecDefaultAction "phase:1,log,auditlog,deny,status:403"/SecDefaultAction "phase:1,log,auditlog,deny,status:403"/g' crs-setup.conf
sed -ie 's/# SecDefaultAction "phase:2,log,auditlog,deny,status:403"/SecDefaultAction "phase:2,log,auditlog,deny,status:403"/g' crs-setup.conf
cat <<EOT >> /etc/nginx/modsecurity.conf
Include owasp-modsecurity-crs/crs-setup.conf
Include owasp-modsecurity-crs/rules/REQUEST-901-INITIALIZATION.conf
Include owasp-modsecurity-crs/rules/REQUEST-903.9002-WORDPRESS-EXCLUSION-RULES.conf
Include owasp-modsecurity-crs/rules/REQUEST-905-COMMON-EXCEPTIONS.conf
Include owasp-modsecurity-crs/rules/REQUEST-912-DOS-PROTECTION.conf
Include owasp-modsecurity-crs/rules/REQUEST-913-SCANNER-DETECTION.conf
Include owasp-modsecurity-crs/rules/REQUEST-930-APPLICATION-ATTACK-LFI.conf
Include owasp-modsecurity-crs/rules/REQUEST-931-APPLICATION-ATTACK-RFI.conf
Include owasp-modsecurity-crs/rules/REQUEST-932-APPLICATION-ATTACK-RCE.conf
Include owasp-modsecurity-crs/rules/REQUEST-933-APPLICATION-ATTACK-PHP.conf
Include owasp-modsecurity-crs/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf
Include owasp-modsecurity-crs/rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf
Include owasp-modsecurity-crs/rules/REQUEST-943-APPLICATION-ATTACK-SESSION-FIXATION.conf
Include owasp-modsecurity-crs/rules/RESPONSE-951-DATA-LEAKAGES-SQL.conf
Include owasp-modsecurity-crs/rules/RESPONSE-953-DATA-LEAKAGES-PHP.conf
EOT

預設ModSecurity並不會阻擋惡意連線

只會記錄在Log

所以最後的sed指令就是在開啟阻擋並回應403

最後幾行就是把要阻擋哪些惡意攻擊的rule include進來

可自行刪減

Step 9. 設定nginx.conf

其實ModSecurity並不是設定在nginx.conf

所以僅供參考即可

mv /etc/nginx/nginx.conf /etc/nginx/nginx.conf.back
cat <<EOT>> /etc/nginx/nginx.conf
user              nginx nginx;
worker_processes  1;

worker_rlimit_nofile 260000;
timer_resolution 100ms;

error_log    /var/log/nginx/nginx_error.log;

events {
    worker_connections  2048;
    accept_mutex on;
    accept_mutex_delay 100ms;
    use epoll;
    #multi_accept on;
}

http {

    # don't send the nginx version number in error pages and Server header
    server_tokens off;

    # config to don't allow the browser to render the page inside an frame or iframe
    # and avoid clickjacking http://en.wikipedia.org/wiki/Clickjacking
    # if you need to allow [i]frames, you can use SAMEORIGIN or even set an uri with ALLOW-FROM uri
    # https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options
    add_header X-Frame-Options SAMEORIGIN;

    # when serving user-supplied content, include a X-Content-Type-Options: nosniff header along with the Content-Type: header,
    # to disable content-type sniffing on some browsers.
    # https://www.owasp.org/index.php/List_of_useful_HTTP_headers
    # currently suppoorted in IE > 8 http://blogs.msdn.com/b/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx
    # http://msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).aspx
    # 'soon' on Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=471020
    add_header X-Content-Type-Options nosniff;

    # This header enables the Cross-site scripting (XSS) filter built into most recent web browsers.
    # It's usually enabled by default anyway, so the role of this header is to re-enable the filter for
    # this particular website if it was disabled by the user.
    # https://www.owasp.org/index.php/List_of_useful_HTTP_headers
    add_header X-XSS-Protection "1; mode=block";

    # with Content Security Policy (CSP) enabled(and a browser that supports it(http://caniuse.com/#feat=contentsecuritypolicy),
    # you can tell the browser that it can only download content from the domains you explicitly allow
    # http://www.html5rocks.com/en/tutorials/security/content-security-policy/
    # https://www.owasp.org/index.php/Content_Security_Policy
    # I need to change our application code so we can increase security by disabling 'unsafe-inline' 'unsafe-eval'
    # directives for css and js(if you have inline css or js, you will need to keep it too).
    # more: http://www.html5rocks.com/en/tutorials/security/content-security-policy/#inline-code-considered-harmful
    # add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://ssl.google-analytics.com https://connect.facebook.net; img-src 'self' https://ssl.google-analytics.com https://s-static.ak.facebook.com ; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://themes.googleusercontent.com; frame-src https://www.facebook.com https://s-static.ak.facebook.com; object-src 'none'";

    #log_format      main    '\$remote_addr - \$remote_user [\$time_local] \$request '
    #                '"\$status" \$body_bytes_sent "\$http_referer" '
    #                '"\$http_user_agent" "\$http_x_forwarded_for" "\$gzip_ratio"'
    #                ' "\$connection" "\$connection_requests" "\$request_time"';

    index  index.php index.html index.htm;
    include       mime.types;
    default_type  application/octet-stream;
    charset utf-8;

    sendfile on;
    #sendfile_max_chunk 1m;
    tcp_nopush  on;
    tcp_nodelay on;
    server_name_in_redirect off;

    keepalive_timeout  10;
    keepalive_requests 100;
    lingering_time 20s;
    lingering_timeout 5s;
    keepalive_disable msie6;

    gzip on;
    gzip_vary   on;
    gzip_disable "MSIE [1-6]\.";
    gzip_static on;
    gzip_min_length   1400;
    gzip_buffers      32 8k;
    gzip_http_version 1.0;
    gzip_comp_level 5;
    gzip_proxied    any;
    gzip_types text/plain text/css text/xml application/javascript application/x-javascript application/xml application/xml+rss application/ecmascript application/json image/svg+xml;

    client_body_buffer_size 256k;
    client_body_in_file_only off;
    client_body_timeout 60s;
    client_header_buffer_size 64k;
    ## how long a connection has to complete sending 
    ## it's headers for request to be processed
    client_header_timeout  20s;
    client_max_body_size  20m; 
    connection_pool_size  512;

    #directio  4m;

    ignore_invalid_headers on;       
    large_client_header_buffers 8 64k;
    output_buffers   8 256k;
    postpone_output  1460;
    #proxy_temp_path  /tmp/nginx_proxy/;
    request_pool_size  32k;
    reset_timedout_connection on;
    send_timeout     60s;
    types_hash_max_size 2048;
    server_names_hash_bucket_size 64;

    # for nginx proxy backends to prevent redirects to backend port 
    # port_in_redirect off;

    open_file_cache max=10000 inactive=30s;
    open_file_cache_valid 120s;
    open_file_cache_min_uses 2;
    open_file_cache_errors off;
    open_log_file_cache max=4096 inactive=30s min_uses=2;

    ### Directive describes the zone, in which the session states are stored i.e. store in slimits. ###
    ### 1m can handle 32000 sessions with 32 bytes/session, set to 5m x 32000 session ###
    limit_conn_zone \$binary_remote_addr zone=slimits:10m;

    ### Control maximum number of simultaneous connections for one session i.e. ###
    ### restricts the amount of connections from a single ip address ###
    limit_conn slimits 10;

    ## Load virtual host conf files. ##
    include /etc/nginx/sites-enabled/*;

    ## Load another configs from conf.d/ ##
    include /etc/nginx/conf.d/*.conf;
}
EOT

Step 10. 設定default.conf

touch /etc/nginx/sites-available/default.conf
cat <<EOT>> /etc/nginx/sites-available/default.conf
server {
    listen 80;
    server_name YOUR_SERVER_NAME

    root   /var/www/html;

    access_log              /var/log/nginx/default.access.log;
    error_log               /var/log/nginx/default.error.log      error;

    location ~ /.well-known {
            allow all;
            break;
    }

    location / {

        index  index.php index.html index.htm;

        ModSecurityEnabled on;
        ModSecurityConfig /etc/nginx/modsecurity.conf;

    }
}
EOT
ln -s /etc/nginx/sites-available/default.conf /etc/nginx/sites-enabled/default.conf
echo "hi" > /var/www/html/index.html

Line 21, 22 就是在開啟ModSecurity並指定config位置

Step 11. 啟動nginx並測試

systemctl start nginx.service

用瀏覽器打上伺服器ip開啟首頁

成功的話會看到hi

這時我們在網址後面自己加?id=1

例如:http://127.0.0.1/?id=1

正常還是會看到hi

但我們在後面再加上 AND 1=1

變成:http://127.0.0.1/?id=1 AND 1=1

就會看到403 Forbidden的訊息了

這就代表安裝成功!