使用Nginx+Lua(openresty)實現自定義WAF


1. Nginx+lua介紹

   Nginx作為一款面向性能設計的HTTP服務器,相較於Apache、lighttpd具有占有內存少,穩定性高等優勢。其流行度越來越高,應用也越來越廣泛,常見的應用有:web服務器、反向代理服務器以及電子郵件(IMAP/POP3)代理服務器,高並發大流量站點常用來做接入層的負載均衡,還有非常常見的用法是作為日志采集服務器等。

  Nginx整體采用模塊化設計,有豐富的模塊庫和第三方模塊庫,配置靈活。注意的是:nginx的模塊是靜態的,添加和刪除模塊都要對nginx進行重新編譯,這一點與Apache的動態模塊完全不同
雖然 Nginx有如此強大的性能以及眾多的三方模塊支持,但每次重新編譯以及尋找三方模塊對生產環境來說還是不可接受的,幸運的是,Nginx 它是支持客戶自己 Lua 腳本編程擴展相應的功能的,而且可以熱加載,這就給生產環境帶來了無限可能。比如我現在想要直接用Nginx + redis 做反爬蟲和頻率限制,Nginx + Kafka 做日志的實時流處理等等。
  Nginx:基於HTTP的負載均衡和反向代理服務器,Nginx工作在網絡的7層,所以它可以針對http應用本身來做分流策略,比如針對域名、URL、目錄結構等,相比之下LVS(基於IP的負載均衡和反向代理技術,工作在網絡4層之上僅作分發之用,負載均衡性能最強,對內存和cpu、IO資源消耗比較低)並不具備這樣的功能,能夠很好地支持虛擬主機,可配置性很強,大約能支持3~5萬條並發連接。
  Lua 是一個簡潔、輕量、可擴展的腳本語言,也是號稱性能最高的腳本語言,用在很多需要性能的地方,比如:游戲腳本,nginx,wireshark的腳本,當你把他的源碼下下來編譯后,你會發現解釋器居然不到200k,非常變態。。。很多應用程序使用Lua作為自己的嵌入式腳本語言,以此來實現可配置性、可擴展性。
  Nginx-Lua模塊的執行順序參考:https://www.cnblogs.com/JohnABC/p/6206622.html

https://blog.csdn.net/njys1/article/details/81413746

1.1 Nginx + Lua部署

   創建nginx運行的普通用戶

groupadd -f nginx
useradd -g nginx nginx

  下載nginx安裝包和依賴包

yum install gcc  gcc-c++ autoconf automake make -y #默認有的話無須安裝
yum install -y  zlib zlib-devel openssl openssl-devel  #依賴(支持Nginx服務訪問,以https方式訪問)pere-devel下載源碼安裝
yum -y install GeoIP GeoIP-devel GeoIP-data #編譯安裝geo ip模塊時會用到,不需要就不安裝
wget -q http://nginx.org/download/nginx-1.15.8.tar.gz #下載nginx軟件包
wget https://nchc.dl.sourceforge.net/project/pcre/pcre/8.42/pcre-8.42.tar.gz #下載最新的pcre依賴

  先不解壓,等安裝完LUAJIT在安裝nginx,加載模塊

  下載當前最新的luajit和ngx_devel_kit(NDK)、lua-nginx-module

  ngx_devel_kit(NDK)模塊是一個拓展nginx服務器核心功能的模塊,第三方模塊開發可以基於它來快速實現。NDK提供函數和宏處理一些基本任務,減輕第三方模塊開發的代碼量。

  ngx_lua_module 是一個nginx http模塊,它把 lua 解析器內嵌到 nginx,用來解析並執行lua 語言編寫的網頁后台腳本,特性很牛叉,可自行百度查看

wget http://luajit.org/download/LuaJIT-2.0.5.tar.gz #下載當前穩定版本
wget https://github.com/simpl/ngx_devel_kit/archive/v0.3.0.tar.gz
wget https://github.com/chaoslawful/lua-nginx-module/archive/v0.10.10.zip
解壓NDK和lua-nginx-module
tar zxvf v0.3.0.tar.gz -C /usr/local   #解壓后為ngx_devel_kit-0.3.0
unzip -q v0.10.10.zip -d /usr/local  #解壓后為lua-nginx-module-0.10.10
安裝LuaJIT Luajit是Lua即時編譯器。
tar zxvf LuaJIT-2.0.5.tar.gz -C /usr/local
cd LuaJIT-2.0.5
make && make install

  安裝nginx並加載模塊

tar zxf nginx-1.15.8.tar.gz
tar zxvf pcre-8.41.tar.gz -C /usr/local
cd nginx-1.15.8
export LUAJIT_LIB=/usr/local/lib
export LUAJIT_INC=/usr/local/include/luajit-2.0
./configure --user=nginx --group=nginx --prefix=/usr/local/nginx-1.15.8/ --with-pcre=/usr/local/pcre-8.42/ --with-http_stub_status_module --with-http_sub_module --with-http_gzip_static_module --with-http_geoip_module --without-mail_pop3_module --without-mail_imap_module --without-mail_smtp_module  --add-module=/usr/local/ngx_devel_kit-0.3.0/ --add-module=/usr/local/lua-nginx-module-0.10.10/
make -j2 && make install
ln -s /usr/local/nginx-1.15.8 /usr/local/nginx
ln -s /usr/local/lib/libluajit-5.1.so.2 /lib64/libluajit-5.1.so.2
ln -s /usr/local/nginx-1.12.2/sbin/nginx /usr/local/sbin/  
nginx #啟動

  如果不創建符號鏈接,可能出現以下異常: error while loading shared libraries: libluajit-5.1.so.2: cannot open shared object file: No such file or directory

  如果需要geo ip模塊的話在nginx.conf加入

http {

    geoip_country /usr/local/nginx/conf/GeoIP.dat;
    geoip_city /usr/local/nginx/conf/GeoLiteCity.dat;
    server_tokens off;
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
     .......
     .......      
}

  測試安裝完成效果,修改nginx.conf增加第一個配置

cd /usr/local/nginx/conf 
egrep -v "^$|#" nginx.conf_2019-03-13  >nginx.conf  #精簡化配置
  location /hello {
                default_type 'text/plain';
                content_by_lua 'ngx.say("hello,lua!!!")';
        }
    
nginx -t
nginx -s reload

  然后訪問http://10.10.16.245/hello 如果出現hello,lua。表示安裝完成。

  注意:也可以直接部署春哥的開源項目:https://github.com/openresty。 

  更多使用 Lua 擴展 Nginx 功能請參考:https://my.oschina.net/leejun2005/blog/494248

  https://github.com/iresty/nginx-lua-module-zh-wiki  #這里着重介紹openresty的使用。

1.2 針對UA進行防護測試

  禁止user-agent使用wget或ab壓測,使用Nginx可以在應用層進行針對請求頭部的UA進行過濾和指定動作(返回403)普通寫法:

server {
        listen       80;
        server_name  localhost;
      ##### 禁止使用wget或ab壓測 #####
        set $block_user_agent 0;
        if ($http_user_agent ~ "Wget|ApacheBench") {
        set $block_user_agent 1;
       }
       if ($block_user_agent = 1) {
        return 403;
      }
.....
.....
}

  終端使用wget和ab進行測試:  可以直接安裝apache的工具包httpd-tools,包含壓測工具ab: yum install httpd-tools

wget 10.10.16.245 #測試是否返回403
ab -n1 -c1 http://10.10.16.245/  #測試結果Non-2xx responses:1 不是200的請求1個,返回403
ab -n100 -c1 http://10.10.16.245/ #cc一下1s請求100次:

1.3 拒絕訪問特定后綴的文件

  自己備份的文件,禁止被download,針對請求的URL進行過濾匹配和指定動作(返回404)

location ~* "\.(sql|bak|inc|old|sh|zip|tgz|gz|tar)$"{

     return 404;

}

1.4 防SQL注入

  傳統的網絡防火牆牆只能夠進行四層(OSI七層模型中的傳輸層)的防護,那么像SQL注入、XSS、網頁掛馬等安全問題卻無法識別和解決,因為這些攻擊是七層的(OSI 七層模型中的應用層)。針對請求的參數進行過濾匹配和指定動作(返回403),我們的url參數中是不可能有select的。

#==Block SQL Injections

set $block_sql_injections 0;

if ($query_string ~ "union.*select.*\("){

   set $block_sql_injections1;

}

if ( $block_sql_injections = 1){

   return 403;

}

 像以上的規則可以寫一些,但是原生態的Nginx的一些安全防護功能還是有限,參考了一下趙班長的waf,通過自定義waf,實現一些基本的安全防護功能。以上所有的防護都可以通過以下openresty配合搭建waf來完成。

2. Openresty介紹

  OpenResty(又稱:ngx_openresty) 是一個基於NGINX的可伸縮的Web平台,由中國人章亦春發起,提供了很多高質量的第三方模塊。360,UPYUN,阿里雲,新浪,騰訊網,去哪兒網,酷狗音樂等都是 OpenResty 的深度用戶。

  OpenResty® 是一個基於 Nginx與 Lua的高性能 Web平台,其內部集成了大量精良的 Lua 庫、第三方模塊以及大多數的依賴項。用於方便地搭建能夠處理超高並發、擴展性極高的動態 Web 應用、Web 服務和動態網關。

  OpenResty® 通過匯聚各種設計精良的 Nginx 模塊(主要由 OpenResty 團隊自主開發),從而將 Nginx 有效地變成一個強大的通用 Web 應用平台。這樣,Web 開發人員和系統工程師可以使用 Lua 腳本語言調動 Nginx 支持的各種 C 以及 Lua 模塊,快速構造出足以勝任 10K 乃至 1000K 以上單機並發連接的高性能 Web 應用系統。

  OpenResty® 的目標是讓你的Web服務直接跑在 Nginx 服務內部,充分利用 Nginx 的非阻塞 I/O 模型,不僅僅對 HTTP 客戶端請求,甚至於對遠程后端諸如 MySQL、PostgreSQL、Memcached 以及 Redis 等都進行一致的高性能響應。
  參考博客地址:
  
http://openresty.org/cn/   #openresty中文社區

  https://blog.csdn.net/yhclt/article/details/79309237 #詳細介紹openresty

  https://github.com/unixhot/waf  #使用Nginx+Lua(openresty)搭建WAF

2.1 openresty部署

  安裝過OpenResty后,Nginx模塊的配置跟上面講的Nginx配置一致。不同的是OpenResty內置了一個ngx_lua模塊,可以通過這個模塊在Nginx中集成lua,完成一些原生Nginx不能完成的工作。

  下載安裝包和依賴包,建議在新的機器上操作: 

  #下載官網提供最新包 http://openresty.org/cn/download.html

創建nginx運行的普通用戶
groupadd -f nginx
useradd -g nginx nginx
yum install gcc  gcc-c++ -y #默認有的話無須安裝
yum install -y readline-devel pcre-devel openssl-devel #依賴(支持Nginx服務訪問,以https方式訪問)pere-devel 下載源碼包
cd /usr/local/src
wget https://nchc.dl.sourceforge.net/project/pcre/pcre/8.42/pcre-8.42.tar.gz #下載最新的pcre依賴
tar xf pcre-8.42.tar.gz -C /usr/local
下載並編譯安裝openresty
wget "https://openresty.org/download/openresty-1.15.8.2.tar.gz" 
tar zxf openresty-1.15.8.2.tar.gz
cd openresty-1.15.8.2

  安裝luajit:Luajit是Lua即時編譯器。這里openresty已經自帶了安裝包,只需要重新編譯一下就可以。

cd bundle/LuaJIT-2.1-20180420/  #進入亂LUAJIT編譯安裝目錄
make clean && make && make install
ln -sf luajit-2.1.0-beta3 /usr/local/bin/luajit

  下載ngx_cache_purge模塊,該模塊用於清理nginx緩存,注意解壓目錄。

cd /usr/local/src
wget https://github.com/FRiCKLE/ngx_cache_purge/archive/2.3.tar.gz
tar xf 2.3.tar.gz -C openresty-1.15.8.2/bundle/

  下載nginx_upstream_check_module模塊,該模塊用於ustream健康檢查,注意解壓目錄。

cd /usr/local/src
wget https://github.com/yaoweibin/nginx_upstream_check_module/archive/v0.3.0.tar.gz
tar xf v0.3.0.tar.gz -C openresty-1.15.8.2/bundle/

  安裝openresty:openresty版本號和標識隱藏(修改):隱藏版本號可以提高安全性。這只需要后面在nginx.conf配置里加上這個就可以了:server_tokens off;

隱藏openresty標識--修改源碼:/usr/local/src/openresty-1.15.8.2/bundle/nginx-1.15.8/src/core/nginx.h   14行將openresty改為nginx:#define NGINX_VER          "nginx/" NGINX_VERSION ".2"
修改錯誤頁的底部Footer:/usr/local/src/openresty-1.15.8.2/bundle/nginx-1.15.8/src/http/ngx_http_special_response.c  36行將openresty改為nginx: "<hr><center>nginx</center>" CRLF
HTTP ResponseHeader:/usr/local/src/openresty-1.15.8.2/bundle/nginx-1.15.8/src/http/ngx_http_header_filter_module.c  49行將"Server: openresty"改為“"Server: nginx"”

  開始編譯安裝:編譯參數根據自己需求再添加

cd openresty-1.15.8.2/
./configure \
--user=nginx --group=nginx \
--prefix=/usr/local/openresty-1.15.8.2 \
--with-luajit \
--with-http_stub_status_module \
--with-http_gzip_static_module \
--without-mail_pop3_module \
--without-mail_imap_module \
--without-mail_smtp_module \
--with-pcre=/usr/local/pcre-8.42 \
--with-pcre-jit \
--with-http_realip_module \
--add-module=./bundle/ngx_cache_purge-2.3/ \
--add-module=./bundle/nginx_upstream_check_module-0.3.0/ \
--without-http_empty_gif_module \
--with-poll_module  \
--with-http_ssl_module \
--with-threads \
--with-file-aio \
--with-poll_module \
--with-http_gunzip_module \
--with-http_auth_request_module \
--with-http_secure_link_module -j2 
gmake && gmake install
ln -s /usr/local/openresty-1.15.8.2 /usr/local/openresty 
ln -s /usr/local/openresty/nginx/sbin/nginx /usr/local/sbin/
cp nginx.conf nginx.conf_`date +%F`
egrep -v "^$|#" nginx.conf_2019-03-13  >nginx.conf  #精簡化配置

2.2 測試openresty安裝

  nginx如何嵌入 lua 腳本。方法就是在nginx的配置文件nginx.conf 中使用content_by_lua或者cotent_by_lua_block指令:

vim /usr/local/openresty/nginx/conf/nginx.conf
server {
    location /hello {
            default_type text/html;
            content_by_lua_block {
                ngx.say("HelloWorld")
            }
        }
}

  lua代碼文件:我們把lua代碼放在nginx配置中會隨着lua的代碼的增加導致配置文件太長不好維護,因此我們應該把lua代碼移到外部文件中存儲。

###test ngx_lua
        location /lua {
        default_type 'text/html';
        content_by_lua_file conf/lua/test.lua; #相對於nginx安裝目錄
        }

  #此處conf/lua/test.lua也可以使用絕對路徑/usr/local/openresty/nginx/conf/lua/test.lua。

nginx -s reload 
curl http://192.168.56.11/lua
hello lua,this is a test!

  至此openresty的基本環境沒有任何問題。

3.WAF部署

  Web應用防護系統(也稱為:網站應用級入侵防御系統。英文:Web Application Firewall,簡稱: WAF)。利用國際上公認的一種說法:Web應用防火牆是通過執行一系列針對HTTP/HTTPS的安全策略來專門為Web應用提供保護的一款產品。詳細介紹請百度百科。

  WAF實現  WAF一句話描述,就是解析HTTP請求(協議解析模塊),規則檢測(規則模塊),做不同的防御動作(動作模塊),並將防御過程(日志模塊)記錄下來。所以本文中的WAF的實現由五個模塊(配置模塊、協議解析模塊、規則模塊、動作模塊、錯誤處理模塊)組成。

   這里的waf部署完成,實現了一些基本可以滿足需求的功能:實現的功能列表如下

  1. 支持IP白名單和黑名單功能,直接將黑名單的IP訪問拒絕。
  2. 支持URL白名單,將不需要過濾的URL進行定義。
  3. 支持User-Agent的過濾,匹配自定義規則中的條目,然后進行處理(返回403)。
  4. 支持CC攻擊防護,單個URL指定時間的訪問次數,超過設定值,直接返回403。
  5. 支持Cookie過濾,匹配自定義規則中的條目,然后進行處理(返回403)。
  6. 支持URL過濾,匹配自定義規則中的條目,如果用戶請求的URL包含這些,返回403。
  7. 支持URL參數過濾,原理同上。
  8. 支持日志記錄,將所有拒絕的操作,記錄到日志中去。
  9. 日志記錄為JSON格式,便於日志分析,例如使用ELKStack進行攻擊日志收集、存儲、搜索和展示。

3.1 部署和測試

  這里直接部署:

cd /usr/local/src
git clone https://github.com/unixhot/waf.git
cp -a ./waf/waf /usr/local/openresty/nginx/conf/

  修改Nginx的配置文件,http模塊加入以下配置。注意路徑,同時WAF日志默認存放在/tmp/日期_waf.log

 ##### WAF #####
    lua_shared_dict limit 50m; #設置lua共享字典50M,cc的時候使用
    lua_package_path "/usr/local/openresty/nginx/conf/waf/?.lua"; #lua依賴路徑
    init_by_lua_file "/usr/local/openresty/nginx/conf/waf/init.lua"; #初始化腳本
    access_by_lua_file "/usr/local/openresty/nginx/conf/waf/access.lua";  #防御main腳本
 ##### WAF #####

  手動測試防護是否生效:

/usr/local/openresty/nginx/sbin/nginx –t
/usr/local/openresty/nginx/sbin/nginx

  cc一下1s請求100次:ab -n100 -c1 http://10.10.16.245/hello   #結果:成功的只有11個

 

   nginx當出現403時返回一個友好的界面:

server {
        listen       80;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
       ####test openresty
        location /hello {
            default_type text/html;
            content_by_lua_block {
                ngx.say("Hello World")
            }
        }
       error_page 403 ./403.html;
       # error_page   500 502 503 504  /50x.html;
       # location = /50x.html {
       #     root   html;
       # }
    }
View Code

  config.lua配置:cat config.lua

--配置模塊 控制開關在這里可以開啟或關閉
--WAF config file,enable = "on",disable = "off"

--waf status
config_waf_enable = "on"
--log dir
config_log_dir = "/tmp"
--rule setting
config_rule_dir = "/usr/local/openresty/nginx/conf/waf/rule-config"
--enable/disable white url
config_white_url_check = "on"
--enable/disable white ip
config_white_ip_check = "on"
--enable/disable block ip
config_black_ip_check = "on"
--enable/disable url filtering
config_url_check = "on"
--enalbe/disable url args filtering
config_url_args_check = "on"
--enable/disable user agent filtering
config_user_agent_check = "on"
--enable/disable cookie deny filtering
config_cookie_check = "on"
--enable/disable cc filtering
config_cc_check = "on"
--cc rate the xxx of xxx seconds 1個ip訪問一個url60s之內只能訪問10次
config_cc_rate = "10/60"
--enable/disable post filtering
config_post_check = "on"
--config waf output redirect/html  --兩種輸出方式,輸出html或跳轉其他頁面
config_waf_output = "redirect"  --此處可以把html換成redirect
--if config_waf_output ,setting url
config_waf_redirect_url = "http://10.10.16.245/403.html"
config_output_html=[[
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
        <meta name="renderer" content="webkit|ie-comp|ie-stand">
        <title>Access forbidden - Pharmacodia</title>
</head>
<style type="text/css"> 
.main{
    text-align: center;
    width: 780px;
    height: 320px;
    margin: auto;
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 200;
    background-color: #eee;
}
.maintxt{
    padding: 10px;
    text-align:left;
    font-size:14px;
    line-height:200%
}
</style>

<div class="main">
    <h2>Pharmacodia could not fulfil your request</h2>
    <hr />
    <p class="maintxt">
    Pharmacodia may reject your requests if you are using any automated tools, perform too many queries per minute or generate queries that result in the system attempting to retrieve unusually large numbers of documents or unusually large documents.<br />
    Please note that multiple users behind a firewall may be seen as a single user who apparently generates many queries in a short time or who downloads excessive amounts of data. As a result it is possible that a colleague other than the user who triggered the error will receive a rejection message.<br />
    If you feel the rejection is not warranted and you would like us to investigate it, please <a href="https://www.pharmacodia.com//web/productBasicInfoShowController/aboutUs_contact_map">contact us</a>.
    </p>
</div>

</html>
]]
View Code

  瀏覽器狂刷10次,看看效果:

3.2 init.lua完善

  默認init.lua里req_data值設的是"-",這里需要手動改一下,可以記錄用戶瀏覽某個頁面:log_record('White_IP',ngx.var_request_uri,ngx.var.uri,"_"),改完效果:

   項目鏈接地址:https://pan.baidu.com/s/1R2XGs754Vq8yiC5NH5krZQ  提取碼: qa36 

3.3 對ip流量進行控制

   可以針對ip進行控制,如只封一個url的話,沒有從根本上解決如何識別一次會話的問題。可以lib.lua 中增加一個add_blackip函數

--- Add black ip ---
function add_blackip(rulefilename,ip)
    local io = require 'io'
    local RULE_PATH = config_rule_dir
    local RULE_FILE = io.open(RULE_PATH..'/'..rulefilename,"a")
    if RULE_FILE == nil then
        return
    end
    RULE_FILE:write(ip.."\n")
    RULE_FILE:flush()
    RULE_FILE:close()
end

  修改 init.lua cc_attack_check()函數,增加下面紅色部分的調用;

  客戶端模擬,CC1s請求100次:

  可發現不成功,再來訪問以下:會直接返回403

  現在我們查看以下剛才添加的執行將用戶ip直接加入黑名單是否成功:

  可以做一個定時,定時清空或轉移blackip.rule文本內容;(這個暫時不用lua實現,可用shell方式)

#!/bin/bash
blackip_file="/usr/local/openresty/nginx/conf/waf/rule-config/blackip.rule"
Num="`grep -v "^$" $blackip_file|wc -l`"
if [ $Num -ge 1 ];then
        echo "`date +%F-%H:%M:%S`" >>/tmp/blackip.log
        echo "--------------------------------"  >>/tmp/blackip.log
    for i in `cat $blackip_file`
    do
        #/usr/bin/python /opt/ip_where.py $i >>/tmp/blackip.log
         echo -e "Black_iplist:      $i" >>/tmp/blackip.log
    done
        echo "--------------------------------"  >>/tmp/blackip.log
        echo  > $blackip_file
        cat /tmp/blackip.log |awk -F ":" '/Black_iplist/{print $2}'|sort|uniq -c|sort -nr >/opt/ip_sort.txt #去重排序
        cat /opt/ip_sort.txt|awk '{print $2}' >/opt/ip.txt #拿到所有ip
else
    exit
fi
View Code

  使用python腳本獲取各ip歸屬地來源:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import urllib
import json
import sys
#淘寶ip庫接口
url = "http://ip.taobao.com/service/getIpInfo.php?ip="
def ip_find(ip):
    data = urllib.urlopen(url + ip).read()
    datadict=json.loads(data)
    for oneinfo in datadict:
        if "code" == oneinfo:
            if datadict[oneinfo] == 0:
                return datadict["data"]["country"] + datadict["data"]["region"] + datadict["data"]["city"] + "\t" + datadict["data"]["isp"]

if __name__ ==  "__main__":
    ip=sys.argv[1]
    name=ip_find(ip)
    print sys.argv[1],name
View Code

  將shell加入定時任務,每五分鍾執行一次,自動解封。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM