使用NGINX+Openresty實現WAF功能
一、了解WAF
1.1 什么是WAF
Web應用防護系統(也稱:網站應用級入侵防御系統 。英文:Web Application Firewall,簡稱: WAF)。利用國際上公認的一種說法:Web應用 防火牆 是通過執行一系列針對HTTP/HTTPS的 安全策略 來專門為Web應用提供保護的一款產品。
1.2 WAF的功能
支持IP白名單和黑名單功能,直接將黑名單的IP訪問拒絕。
支持URL白名單,將不需要過濾的URL進行定義。
支持User-Agent的過濾,匹配自定義規則中的條目,然后進行處理(返回403)。
支持CC攻擊防護,單個URL指定時間的訪問次數,超過設定值,直接返回403。
支持Cookie過濾,匹配自定義規則中的條目,然后進行處理(返回403)。
支持URL過濾,匹配自定義規則中的條目,如果用戶請求的URL包含這些,返回403。
支持URL參數過濾,原理同上。
支持日志記錄,將所有拒絕的操作,記錄到日志中去
1.3 WAF的特點
異常檢測協議
Web應用防火牆會對HTTP的請求進行異常檢測,拒絕不符合HTTP標准的請求。並且,它也可以只允許HTTP協議的部分選項通過,從而減少攻擊的影響范圍。甚至,一些Web應用防火牆還可以嚴格限定HTTP協議中那些過於松散或未被完全制定的選項。
增強的輸入驗證
增強輸入驗證,可以有效防止網頁篡改、信息泄露、木馬植入等惡意網絡入侵行為。從而減小Web服務器被攻擊的可能性。
及時補丁
修補Web安全漏洞,是Web應用開發者最頭痛的問題,沒人會知道下一秒有什么樣的漏洞出現,會為Web應用帶來什么樣的危害。WAF可以為我們做這項工作了——只要有全面的漏洞信息WAF能在不到一個小時的時間內屏蔽掉這個漏洞。當然,這種屏蔽掉漏洞的方式不是非常完美的,並且沒有安裝對應的補丁本身就是一種安全威脅,但我們在沒有選擇的情況下,任何保護措施都比沒有保護措施更好。
基於規則的保護和基於異常的保護
基於規則的保護可以提供各種Web應用的安全規則,WAF生產商會維護這個規則庫,並時時為其更新。用戶可以按照這些規則對應用進行全方面檢測。還有的產品可以基於合法應用數據建立模型,並以此為依據判斷應用數據的異常。但這需要對用戶企業的應用具有十分透徹的了解才可能做到,可現實中這是十分困難的一件事情。
狀態管理
WAF能夠判斷用戶是否是第一次訪問並且將請求重定向到默認登錄頁面並且記錄事件。通過檢測用戶的整個操作行為我們可以更容易識別攻擊。狀態管理模式還能檢測出異常事件(比如登陸失敗),並且在達到極限值時進行處理。這對暴力攻擊的識別和響應是十分有利的。
其他防護技術
WAF還有一些安全增強的功能,可以用來解決WEB程序員過分信任輸入數據帶來的問題。比如:隱藏表單域保護、抗入侵規避技術、響應監視和信息泄露保護。
1.3WAF與網絡防火牆的區別
網絡防火牆作為訪問控制設備,主要工作在OSI模型三、四層,基於IP報文進行檢測。只是對端口做限制,對TCP協議做封堵。其產品設計無需理解HTTP會話,也就決定了無法理解Web應用程序語言如HTML、SQL語言。因此,它不可能對HTTP通訊進行輸入驗證或攻擊規則分析。針對Web網站的惡意攻擊絕大部分都將封裝為HTTP請求,從80或443端口順利通過防火牆檢測。
一些定位比較綜合、提供豐富功能的防火牆,也具備一定程度的應用層防御能力,如能根據TCP會話異常性及攻擊特征阻止網絡層的攻擊,通過IP分拆和組合也能判斷是否有攻擊隱藏在多個數據包中,但從根本上說他仍然無法理解HTTP會話,難以應對如SQL注入、跨站腳本、cookie竊取、網頁篡改等應用層攻擊。
web應用防火牆能在應用層理解分析HTTP會話,因此能有效的防止各類應用層攻擊,同時他向下兼容,具備網絡防火牆的功能。
二、使用nginx配置簡單實現403和404
2.1 小試身手之rerurn 403
修改nginx配置文件在server中加入以下內容
set $block_user_agent 0;
if ($http_user_agent ~ "Wget|AgentBench"){ # 注意if 和(之間要有空格,否則會報錯
set $block_user_agent 1;
}
if ($block_user_agent = 1){
return 403;
}
通過其他機器去wget,結果如下
[root@mini1 ~]# wget http://192.168.3.140
--2017-05-05 14:14:53-- http://192.168.3.140/
Connecting to 192.168.3.140:80... connected.
HTTP request sent, awaiting response... 403 Forbidden
2017-05-05 14:14:53 ERROR 403: Forbidden.
2.2小試身手之rerurn 404
在nginx配置文件中加入如下內容,讓訪問sql|bak|zip|tgz|tar.gz的請求返回404
location ~* "\.(sql|bak|zip|tgz|tar.gz)$" {
return 404;
}
NGINX下如何自定義404頁面:
1.創建自己的404.html頁面
2.更改nginx.conf在http定義區域加入: fastcgi_intercept_errors on;
3.更改nginx.conf
中在server 區域加入: error_page 404 /404.html 或者 error_page 404 = http://www.xxx.com/404.html
4.更改后重啟nginx,,測試nginx.conf正確性: /user/local/nginx/sbin/nginx –t
在網站根目錄下放一個.sql
通過瀏覽器訪問結果如下,404已生效
三、深入實現WAF
3.1 WAF實現規划
分析步驟如下:解析HTTP請求==》匹配規則==》防御動作==》記錄日志
具體實現如下:
解析http請求:協議解析模塊
匹配規則:規則檢測模塊,匹配規則庫
防御動作:return 403 或者跳轉到自定義界面
日志記錄:記錄到elk中,畫出餅圖,建議使用json格式
3.2安裝nginx+lua
由於nginx配置文件書寫不方便,並且實現白名單功能很復雜,nginx的白名單也不適用於CC攻擊,所以在這里使用nginx+lua來實現WAF,如果想使用lua,須在編譯nginx的時候配置上lua,或者結合OpenResty使用,此方法不需要編譯nginx時候指定lua
3.2.1 編譯nginx的時候加載lua可以參考
安裝依賴包
yum install -y gcc gcc-c++ openssl-devel
環境准備 [root@nginx-lua ~]# cd /usr/local/src 首先,現在Nginx安裝必備的Nginx和PCRE軟件包。
[root@nginx-lua src]# wget http://nginx.org/download/nginx-1.9.4.tar.gz
[root@nginx-lua src]# wget ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-8.37.tar.gz
其次,下載當前最新的luajit和ngx_devel_kit (NDK),以及lua-nginx-module
[root@nginx-lua src]# wget http://luajit.org/download/LuaJIT-2.0.4.tar.gz
[root@nginx-lua src]# wget https://github.com/simpl/ngx_devel_kit/archive/v0.2.19.tar.gz
[root@nginx-lua src]# wget https://github.com/openresty/lua-nginx-module/archive/v0.9.16.tar.gz
最后,創建Nginx運行的普通用戶 [root@nginx-lua src]# useradd -s /sbin/nologin -M www
解壓NDK和lua-nginx-module
[root@node1 src]# tar zxvf v0.2.19.tar.gz # 解壓后為ngx_devel_kit-0.2.19
[root@node1 src]# tar -zxvf v0.9.16.tar.gz # 解壓后為lua-nginx-module-0.9.16
[root@node1 src]# tar zxf pcre-8.37.tar.gz
安裝LuaJIT Luajit是Lua即時編譯器
[root@node1 src]# tar zxvf LuaJIT-2.0.4.tar.gz
[root@node1 src]# cd LuaJIT-2.0.4
[root@node1 LuaJIT-2.0.4]# make && make install
安裝Nginx並加載模塊(說明該編譯方式只能適配nginx1.9.4版本的nginx,1.10和1.12等無法適配)
[root@node1 nginx-1.9.4]# tar zxf nginx-1.9.4.tar.gz
[root@node1 src]# cd cd nginx-1.9.4
[root@node1 nginx-1.9.4]# export LUAJIT_LIB=/usr/local/lib
[root@node1 nginx-1.9.4]# export LUAJIT_INC=/usr/local/include/luajit-2.0
[root@node1 nginx-1.9.4]# ./configure --prefix=/usr/local/nginx --user=www --group=www --with-http_ssl_module --with-http_stub_status_module --with-file-aio --with-http_dav_module --add-module=../ngx_devel_kit-0.2.19/ --add-module=../lua-nginx-module-0.9.16/ --with-pcre=/usr/local/src/pcre-8.37
[root@node1 nginx-1.9.4]# make -j2 && make install # -j2表示同時使用兩個任務
[root@openstack-compute-node5 ~]# ln -s /usr/local/lib/libluajit-5.1.so.2 /lib64/libluajit-5.1.so.2 #一定創建此軟連接,否則報錯
安裝完畢后,下面可以測試安裝了,修改nginx.conf 增加第一個配置
location /hello {
default_type 'text/plain';
content_by_lua 'ngx.say("hello,lua,heihei")';
}
[root@node1 nginx]# /usr/local/nginx-1.9.4/sbin/nginx –t
[root@node1 nginx]# /usr/local/nginx-1.9.4/sbin/nginx
3.2.3 Openresty部署
安裝依賴包
# yum install -y readline-devel pcre-devel openssl-devel
下載並編譯安裝openresty(ngx_openresty-1.9.3.2.tar.gz和nginx1.9.4兼容,其他版本無法和nginx1.9.4兼容)
[root@node1 html]# cd /usr/local/src/
[root@node1 src]# wget https://openresty.org/download/ngx_openresty-1.9.3.2.tar.gz
[root@node1 src]# tar -zxf ngx_openresty-1.9.3.2.tar.gz
[root@node1 src]# cd ngx_openresty-1.9.3.2
[root@node1 ngx_openresty-1.9.3.2]# ./configure --prefix=/usr/local/openresty-1.9.3.2 --with-luajit --with-http_stub_status_module --with-pcre --with-pcre-jit
[root@node1 ngx_openresty-1.9.3.2]# gmake && gmake install
[root@node1 ngx_openresty-1.9.3.2]# ln -s /usr/local/openresty-1.9.3.2/ /usr/local/openresty
測試openresty安裝
location /hi {
default_type text/html;
content_by_lua_block{
ngx.say('hello openrastry')
}
}
測試並啟動openrestry下的nginx
[root@node1 ngx_openresty-1.9.3.2]# /usr/local/openresty/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/openresty-1.9.3.2/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/openresty-1.9.3.2/nginx/conf/nginx.conf test is successful
[root@node1 ngx_openresty-1.9.3.2]# pkill nginx
[root@node1 ngx_openresty-1.9.3.2]# /usr/local/openresty/nginx/sbin/nginx
3.2.4WAF部署
在github上克隆下代碼
[root@node1 ~]# git clone https://github.com/unixhot/waf.git
Cloning into 'waf'...
remote: Counting objects: 75, done.
remote: Total 75 (delta 0), reused 0 (delta 0), pack-reused 75
Unpacking objects: 100% (75/75), done.
[root@node1 ~]# cp -a ./waf/waf /usr/local/openresty/nginx/conf/
修改Nginx的配置文件,加入(http字段)以下配置。注意路徑,同時WAF日志默認存放在/tmp/日期_waf.log
[root@node1 ~]# vim /usr/local/openresty/nginx/conf/nginx.conf
#WAF
lua_shared_dict limit 50m; #防cc使用字典,大小50M
lua_package_path "/usr/local/openresty/nginx/conf/waf/?.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";
[root@node1 ~]# /usr/local/openresty/nginx/sbin/nginx -t
[root@node1 ~]# /usr/local/openresty/nginx/sbin/nginx -s reload
根據日志記錄位置,創建日志目錄
[root@node1 ~]# mkdir /tmp/waf_logs
[root@node1 ~]# chown -R www.www /tmp/waf_logs
3.3學習模塊
3.3.1學習配置模塊
WAF上生產之前,建議不要直接上生產,而是先記錄日志,不做任何動作。確定WAF不產生誤殺
config.lua即WAF功能詳解
# pwd
# /usr/local/openresty/nginx/conf/waf
[root@node1 waf]# vim config.lua
--WAF config file,enable = "on",disable = "off"
--waf status
config_waf_enable = "on" #是否開啟配置
--log dir
config_log_dir = "/tmp/waf_logs" #日志記錄地址
--rule setting
config_rule_dir = "/usr/local/nginx/conf/waf/rule-config"
#匹配規則縮放地址
--enable/disable white url
config_white_url_check = "on" #是否開啟url檢測
--enable/disable white ip
config_white_ip_check = "on" #是否開啟IP白名單檢測
--enable/disable block ip
config_black_ip_check = "on" #是否開啟ip黑名單檢測
--enable/disable url filtering
config_url_check = "on" #是否開啟url過濾
--enalbe/disable url args filtering
config_url_args_check = "on" #是否開啟參數檢測
--enable/disable user agent filtering
config_user_agent_check = "on" #是否開啟ua檢測
--enable/disable cookie deny filtering
config_cookie_check = "on" #是否開啟cookie檢測
--enable/disable cc filtering
config_cc_check = "on" #是否開啟防cc攻擊
--cc rate the xxx of xxx seconds
config_cc_rate = "10/60" #允許一個ip60秒內只能訪問10此
--enable/disable post filtering
config_post_check = "on" #是否開啟post檢測
--config waf output redirect/html
config_waf_output = "html" #action一個html頁面,也可以選擇跳轉
--if config_waf_output ,setting url
config_waf_redirect_url = "http://www.baidu.com"
config_output_html=[[ #下面是html的內容
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="zh-cn" />
<title>網站防火牆</title>
</head>
<body>
<h1 align="center"> 您的行為已違反本網站相關規定,注意操作規范。</h1>
</body>
</html>
]]
3.4 學習access.lua的配置
[root@node1 waf]# pwd
/usr/local/openresty/nginx/conf/waf
[root@node1 waf]# cat access.lua
require 'init'
function waf_main()
if white_ip_check() then
elseif black_ip_check() then
elseif user_agent_attack_check() then
elseif cc_attack_check() then
elseif cookie_attack_check() then
elseif white_url_check() then
elseif url_attack_check() then
elseif url_args_attack_check() then
--elseif post_attack_check() then
else
return
end
end
waf_main()
順序:先檢查白名單,通過即不檢測;再檢查黑名單,不通過即拒絕,檢查UA,UA不通過即拒絕;檢查cookie;URL檢查;URL參數檢查,post檢查;
3.5 啟用WAF並測試
3.5.1模擬sql注入即url攻擊
顯示效果如下
日志顯示如下,記錄了UA,匹配規則,URL,客戶端類型,攻擊的類型,請求的數據
[root@node1 waf]# tail -f /tmp/waf_logs/2017-05-05_waf.log
{"user_agent":"Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/57.0.2987.133 Safari\/537.36","rule_tag":"\\.(bak|inc|old|mdb|sql|backup|java|class|tgz|gz|tar|zip)$","req_url":"\/abc.sql","client_ip":"192.168.3.84","local_time":"2017-05-05 17:06:28","attack_method":"Deny_URL","req_data":"-","server_name":"localhost"}
{"user_agent":"Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/57.0.2987.133 Safari\/537.36","rule_tag":"\\.(bak|inc|old|mdb|sql|backup|java|class|tgz|gz|tar|zip)$","req_url":"\/abc.sql","client_ip":"192.168.3.84","local_time":"2017-05-05 17:06:29","attack_method":"Deny_URL","req_data":"-","server_name":"localhost"}
{"user_agent":"Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/57.0.2987.133 Safari\/537.36","rule_tag":"\\.(bak|inc|old|mdb|sql|backup|java|class|tgz|gz|tar|zip)$","req_url":"\/abc.sql","client_ip":"192.168.3.84","local_time":"2017-05-05 17:06:29","attack_method":"Deny_URL","req_data":"-","server_name":"localhost"}
3.5.2 使用ab壓測工具模擬防cc攻擊
[root@mini1 ~]# ab -n 1000 -c 50 http://192.168.3.140/ This is ApacheBench, Version 2.3 <$Revision: 655654 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 192.168.3.140 (be patient) Completed 100 requests Completed 200 requests Completed 300 requests Completed 400 requests Completed 500 requests Completed 600 requests Completed 700 requests Completed 800 requests Completed 900 requests Completed 1000 requests Finished 1000 requests Server Software: openresty/1.9.3.2 Server Hostname: 192.168.3.140 Server Port: 80 Document Path: / Document Length: 612 bytes Concurrency Level: 50 Time taken for tests: 0.674 seconds Complete requests: 1000 Failed requests: 989 (Connect: 0, Receive: 0, Length: 989, Exceptions: 0) Write errors: 0 Non-2xx responses: 989 # config.lua中設置的,60秒內只允許10個請求 Total transferred: 334731 bytes HTML transferred: 178818 bytes Requests per second: 1483.92 [#/sec] (mean) Time per request: 33.695 [ms] (mean) Time per request: 0.674 [ms] (mean, across all concurrent requests) Transfer rate: 485.07 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 1 1.0 1 7 Processing: 4 32 3.7 32 38 Waiting: 2 32 3.8 32 38 Total: 11 33 3.0 33 41 Percentage of the requests served within a certain time (ms) 50% 33 66% 34 75% 34 80% 34 90% 35 95% 36 98% 37 99% 38 100% 41 (longest request)
3.5.3 模擬ip黑名單
將請求ip放入ip黑名單中
[root@node1 waf]# echo "192.168.3.84" >>/usr/local/openresty/nginx/conf/waf/rule-config/blackip.rule
顯示結果如下
3.5.4 模擬ip白名單
將請求ip放入ip白名單中,此時將不對此ip進行任何防護措施,所以sql注入時應該返回404
[root@node1 waf]# echo "192.168.3.84" >> /usr/local/openresty/nginx/conf/waf/rule-config/whiteip.rule
顯示結果如下
3.5.5 模擬URL參數檢測
瀏覽器輸入http://192.168.3.140/?id=select * from name where name="jack"
顯示結果如下
詳細規定在arg.rule中有規定,對請求進行了規范
[root@node1 rule-config]# cat /usr/local/openresty/nginx/conf/waf/rule-config/args.rule \.\./ \:\$ \$\{ select.+(from|limit) (?:(union(.*?)select)) having|rongjitest sleep\((\s*)(\d*)(\s*)\) benchmark\((.*)\,(.*)\) base64_decode\( (?:from\W+information_schema\W) (?:(?:current_)user|database|schema|connection_id)\s*\( (?:etc\/\W*passwd) into(\s+)+(?:dump|out)file\s* group\s+by.+\( xwork.MethodAccessor (?:define|eval|file_get_contents|include|require|require_once|shell_exec|phpinfo|system|passthru|preg_\w+|execute|echo|print|print_r|var_dump|(fp)open|alert|showmodaldialog)\( xwork\.MethodAccessor (gopher|doc|php|glob|file|phar|zlib|ftp|ldap|dict|ogg|data)\:\/ java\.lang \$_(GET|post|cookie|files|session|env|phplib|GLOBALS|SERVER)\[ \<(iframe|script|body|img|layer|div|meta|style|base|object|input) (onmouseover|onerror|onload)\=
四、防cc攻擊利器之httpgrard
4.1 httpgrard介紹
HttpGuard是基於openresty,以lua腳本語言開發的防cc攻擊軟件。而openresty是集成了高性能web服務器Nginx,以及一系列的Nginx模塊,這其中最重要的,也是我們主要用到的nginx lua模塊。HttpGuard基於nginx lua開發,繼承了nginx高並發,高性能的特點,可以以非常小的性能損耗來防范大規模的cc攻擊。
4.2 httpgrard防cc特效
限制訪客在一定時間內的請求次數
向訪客發送302轉向響應頭來識別惡意用戶,並阻止其再次訪問
向訪客發送帶有跳轉功能的js代碼來識別惡意用戶,並阻止其再次訪問
向訪客發送cookie來識別惡意用戶,並阻止其再次訪問
支持向訪客發送帶有驗證碼的頁面,來進一步識別,以免誤傷
支持直接斷開惡意訪客的連接
支持結合iptables來阻止惡意訪客再次連接
支持白名單功能
支持根據統計特定端口的連接數來自動開啟或關閉防cc模式
詳見github地址https://github.com/centos-bz/HttpGuard
五、WAF上線
初期上線只記錄日志,不開啟WAF,防止誤殺
WAF規則管理使用saltstack工具
要知道並不是有了WAF就安全,安全在很大一部分是人為因素