.tw活動

使用NAXSI建置簡易WAF的網頁伺服器

前言:

現在的電腦使用環境,已是網際網路盛行的年代,無論是各大公司行號或個人,幾乎將擁有的各種訊息全都連上網路。雖然在某些機密或重要的伺服器,都會加上各種防護機制(例如:帳號/密碼控制、防火牆、入侵偵測...等),但,網頁應用程式是「人」撰寫,而「人」的思考難免會有些盲點、或偷懶對於輸入的資料不做檢核,而讓某些有心人士可以透過正常的瀏覽網頁行為,輸入一些特殊字串,導致系統發生不可預期的錯誤,將一些機密資料外洩,而造成資安漏洞。為了解決這個問題,有些企業會添購網頁應用程式防火牆(Web Application Firewall, WAF)來防護,雖然可以有效阻擋一些攻擊,然而,該設備所費不貲,並非一般企業所能負擔。因此,本文章會介紹使用NAXSI模組建置簡易的WAF系統,阻擋一些常見的XSS及SQL Injection攻擊。

示範說明:

一、示範的環境及網域名稱:

  1. 網域名稱:epaper.idv.tw
  2. 作業系統:FreeBSD v11.0
  3. 網頁伺服器:Nginx v1.12.0

二、參考相關資料:

  1. Nginx網站:http://nginx.org/
  2. NAXSI網站:https://github.com/nbs-system/naxsi
  3. Nxapi工具:https://github.com/nbs-system/naxsi/tree/master/nxapi
  4. Elasticsearch工具:https://www.elastic.co/products/elasticsearch

三、備註:

  1. NAXSI是Nginx的第三方模組,採用GPLv3授權,可以免費使用。
  2. NAXSI只能過濾「GET」及「POST」的請求。

安裝及設定方式

  1. 安裝Nginx-Naxsi軟體
    安裝nginx-naxsi:
    # 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

    重新啟動Hostname:
    # /etc/rc.d/hostname restart

  2. 設定Nginx-Naxsi
    修改nginx.conf檔:
    # 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;
    }

  3. 建立Naxsi的自訂規則檔:(檔名自訂)
    # 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;
    備註:
    1. 若只要學習不阻擋的話,將「#LearningMode;」改為「LearningMode;」。
    2. 「/RequestDenied」要搭配epaper.idv.tw.conf裡的「location /RequestDenied」使用。
    3. 設定阻擋的分數,可以自行調整。

    建立epaper.idv.tw的網站設定檔:(檔名自訂,只要後面是「.conf」結尾即可)
    # 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;
    備註:
    1. 「location /RequestDenied」要搭配epaper.idv.tw.rules裡的「DeniedUrl "/RequestDenied";」設定。這樣在access.log中,被阻擋的攻擊才會出現400;若將Rules改用「DeniedUrl "/err-50x.html";」,則該攻擊只會出現200,這樣容易造成誤判,以為該次攻擊已成功。
    2. include /usr/local/etc/nginx/naxsi-rules/epaper.idv.tw.rules;」只能加在「location {}」的設定區塊裡。

    新增解析PHP的設定檔:(檔名可自訂)
    # 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;
    }

    新增err-400.html檔:(範例檔,其他的範例可以參考https://github.com/AndiDittrich/HttpErrorPages)
    # 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>

  4. 修改rc.conf檔
    # vi /etc/rc.conf
    php_fpm_enable="YES"
    nginx_enable="YES"

  5. 啟動服務
    # service php-fpm start
    # service nginx start

測試阻擋效果

  1. 建立測試頁:(這只是想呈現輸入的參數,可以依自己的需求建立測試頁面)
    # 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>

  2. 測試一:(使用SQL註解符號「--」)
    測試字串
    http://epaper.idv.tw/index.php?asd=----

    原本的效果:
    畫面:


    Access.log資料:

    加了「include /usr/local/etc/nginx/naxsi-rules/epaper.idv.tw.rules;」的效果:
    畫面:


    Access.log資料:
    備註:該次訪問,網頁伺服器回應400

    Error.log資料:

    備註:
    1. NAXSI拒絕該次訪問時,可以在error.log記錄中找到標記「NAXSI_FMT」的訊息內容。
    2. 若epaper.idv.tw.rules中設定「LearningMode;」時,記錄會變成「learning=1」。
    3. 「block=1」表示已阻擋該次訪問。
    4. 該次訪問,是以「$SQL」來判斷。此次的分數總計達12分,已超過設定分數(8分)。

  3. 測試二:(使用XSS)
    測試字串
    http://epaper.idv.tw/index.php?var1=<script>alert('test');</script>

    原本的效果:
    畫面:


    Access.log資料:

    加了「include /usr/local/etc/nginx/naxsi-rules/epaper.idv.tw.rules;」的效果:
    畫面:


    Access.log資料:
    備註:該次訪問,網頁伺服器回應400

    Error.log資料:

    備註:該次訪問,是以「$XSS」來判斷。此次的分數總計達8分,剛好符合設定的阻擋分數(8分)。

分析阻擋的效果

  1. 安裝所需要的工具及啟動服務
    安裝elasticsearch及相關套件
    # 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

    修改rc.conf檔:
    # vi /etc/rc.conf
    elasticsearch_enable="YES"

    啟動服務
    # service elasticsearch start

  2. 下載nxapi工具並分析
    下載nxapi工具:(若系統沒有wget或unzip指令,請自行安裝對應的套件)
    # wget  https://github.com/nbs-system/naxsi/archive/master.zip
    # unzip master.zip
    # cd naxsi-master/nxapi/

    修改nxapi.json檔:
    # vi nxapi.json
    "rules_path" :  "/usr/local/etc/nginx/naxsi_core.rules",

    更新geoip的資料:
    # /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/'
財團法人台灣網路資訊中心,100臺北市羅斯福路二段9號4樓之2
TEL:886-2-23411313,FAX:886-2-2396-8832,版權聲明,禁止未經授權轉貼節錄