使用NAXSI建置簡易WAF的網頁伺服器
前言:
現在的電腦使用環境,已是網際網路盛行的年代,無論是各大公司行號或個人,幾乎將擁有的各種訊息全都連上網路。雖然在某些機密或重要的伺服器,都會加上各種防護機制(例如:帳號/密碼控制、防火牆、入侵偵測...等),但,網頁應用程式是「人」撰寫,而「人」的思考難免會有些盲點、或偷懶對於輸入的資料不做檢核,而讓某些有心人士可以透過正常的瀏覽網頁行為,輸入一些特殊字串,導致系統發生不可預期的錯誤,將一些機密資料外洩,而造成資安漏洞。為了解決這個問題,有些企業會添購網頁應用程式防火牆(Web Application Firewall, WAF)來防護,雖然可以有效阻擋一些攻擊,然而,該設備所費不貲,並非一般企業所能負擔。因此,本文章會介紹使用NAXSI模組建置簡易的WAF系統,阻擋一些常見的XSS及SQL Injection攻擊。
示範說明:
一、示範的環境及網域名稱:
二、參考相關資料:
三、備註:
安裝及設定方式
# cd /usr/ports/www/nginx-naxsi/ && make install clean |
# mkdir -p /var/log/nginx # mkdir -p /usr/local/etc/nginx/vhost.d # mkdir -p /usr/local/etc/nginx/naxsi-rules # mkdir -p /usr/local/etc/nginx/templates |
# /etc/rc.d/hostname restart |
# vi /usr/local/etc/nginx/nginx.conf |
user www www;
worker_processes 4;
events {
use kqueue;
worker_connections 2048;
}
http {
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_iso8601] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'"$host" $request_time';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log;
### 這一行是naxsi的核心規則
include /usr/local/etc/nginx/naxsi_core.rules;
charset utf-8;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_names_hash_bucket_size 128;
client_header_buffer_size 2k;
server_tokens off;
add_header X-Frame-Options SAMEORIGIN;
gzip on;
gzip_vary on;
gzip_comp_level 3;
gzip_disable "MSIE [1-6]\.";
gzip_types text/xml text/plain text/css text/javascript application/javascript application/x-javascript application/rss+xml application/x-httpd-php image/jpeg image/gif image/png;
upstream php_workers {
### 這一行要搭配/usr/local/etc/php-fpm.d/www.conf裡的「listen」設定
server unix:/tmp/php7-fpm.sock;
}
server {
listen 80 default;
return 444;
root /var/empty;
}
include /usr/local/etc/nginx/vhost.d/*.conf;
} |
# vi /usr/local/etc/nginx/naxsi-rules/epaper.idv.tw.rules |
### Sample rules file for default vhost. #LearningMode; SecRulesEnabled; DeniedUrl "/RequestDenied"; ### check rules CheckRule "$SQL >= 8" BLOCK; CheckRule "$RFI >= 8" BLOCK; CheckRule "$TRAVERSAL >= 4" BLOCK; CheckRule "$EVADE >= 4" BLOCK; CheckRule "$XSS >=8" BLOCK; |
備註:
|
# vi /usr/local/etc/nginx/vhost.d/epaper.idv.tw.conf |
server { server_name epaper.idv.tw; client_body_timeout 5s; client_header_timeout 5s; root /usr/local/www/epaper.idv.tw; index index.php index.html index.htm; location = /favicon.ico { log_not_found off; access_log off; } error_page 400 /err-400.html; error_page 403 /err-403.html; error_page 404 /err-404.html; ### redirect server error pages to the static page /50x.html error_page 500 502 503 504 /err-50x.html; location = /50x.html { root /usr/local/www/epaper.idv.tw; } location ~ /\.ht { deny all; } location /RequestDenied { return 400; } location / { include /usr/local/etc/nginx/naxsi-rules/epaper.idv.tw.rules; } include /usr/local/etc/nginx/templates/php-catchall.tmpl; |
備註:
|
# vi /usr/local/etc/nginx/templates/php-catchall.tmpl |
location ~ \.php$ {
try_files $uri =404;
include fastcgi_params;
include /usr/local/etc/nginx/naxsi-rules/epaper.idv.tw.rules;
fastcgi_index index.php;
fastcgi_pass php_workers;
fastcgi_split_path_info ^(.+\.php)(.*)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
} |
# vi /usr/local/www/epaper.idv.tw/err-400.html |
<!DOCTYPE html> <html lang="en"> <head> <!-- Simple HttpErrorPages | MIT X11 License | https://github.com/AndiDittrich/HttpErrorPages --> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>We've got some trouble | 400 - Bad Request</title> <style type="text/css">/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}/*! Simple HttpErrorPages | MIT X11 License | https://github.com/AndiDittrich/HttpErrorPages */body,html{width:100%;height:100%;background-color:#21232a}body{color:#fff;text-align:center;text-shadow:0 2px 4px rgba(0,0,0,.5);padding:0;min-height:100%;-webkit-box-shadow:inset 0 0 75pt rgba(0,0,0,.8);box-shadow:inset 0 0 75pt rgba(0,0,0,.8);display:table;font-family:"Open Sans",Arial,sans-serif}h1{font-family:inherit;font-weight:500;line-height:1.1;color:inherit;font-size:36px}h1 small{font-size:68%;font-weight:400;line-height:1;color:#777}a{text-decoration:none;color:#fff;font-size:inherit;border-bottom:dotted 1px #707070}.lead{color:silver;font-size:21px;line-height:1.4}.cover{display:table-cell;vertical-align:middle;padding:0 20px}footer{position:fixed;width:100%;height:40px;left:0;bottom:0;color:#a0a0a0;font-size:14px}</style> </head> <body> <div class="cover"> <h1>Bad Request <small>Error 400</small></h1> <p class="lead">The server cannot process the request due to something that is perceived to be a client error.</p> </div> </body> </html> |
# vi /etc/rc.conf |
php_fpm_enable="YES" nginx_enable="YES" |
# service php-fpm start # service nginx start |
測試阻擋效果
# vi /usr/local/www/epaper.idv.tw/index.php |
<?php $count = 0; foreach ( $_GET as $key => $value) { $count++; print( "Var-$count<br/>" ); print( "key=$key<br/>value=$value<br/><br/>" ); } ?> <html> <head> <title>Welcome to nginx!</title> <style> body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } </style> </head> <body> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p> <p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/> Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p> </body> </html> |
http://epaper.idv.tw/index.php?asd=---- |
畫面: Access.log資料: |
畫面: Access.log資料: |
備註:該次訪問,網頁伺服器回應400 |
Error.log資料: |
備註:
|
http://epaper.idv.tw/index.php?var1=<script>alert('test');</script> |
畫面: Access.log資料: |
畫面: Access.log資料: |
備註:該次訪問,網頁伺服器回應400 |
Error.log資料: |
備註:該次訪問,是以「$XSS」來判斷。此次的分數總計達8分,剛好符合設定的阻擋分數(8分)。 |
分析阻擋的效果
# cd /usr/ports/textproc/elasticsearch5/ && make install clean # cd /usr/ports/textproc/py-elasticsearch-py/ && make install clean # cd /usr/ports/net/py-GeoIP/ && make install clean |
# vi /etc/rc.conf |
elasticsearch_enable="YES" |
# service elasticsearch start |
# wget https://github.com/nbs-system/naxsi/archive/master.zip # unzip master.zip # cd naxsi-master/nxapi/ |
# vi nxapi.json |
"rules_path" : "/usr/local/etc/nginx/naxsi_core.rules", |
# /usr/local/bin/geoipupdate.sh |
第一步:先檢查elasticsearch是否正常 # /usr/local/bin/curl -XGET http://localhost:9200/ 第二步:建立nxapi表格 # /usr/local/bin/curl -XPUT 'http://localhost:9200/nxapi/' 第三步:匯入NAXSI Log資料 # /usr/local/bin/python2 ./nxtool.py -c nxapi.json --files=/var/log/nginx/error.log 第四步:統計資料 # /usr/local/bin/python2 ./nxtool.py -c nxapi.json -x |
檢查nxapi表格的資料: # /usr/local/bin/curl -XPOST "http://localhost:9200/nxapi/events/_search?pretty" -d '{}' 刪除nxapi表格的所有資料: # /usr/local/bin/curl -XDELETE 'http://localhost:9200/nxapi/' |