使用Nginx+Lua实现自定义WAF(Web application firewall)
参考网址https://github.com/unixhot/waf
WAF一句话描述,就是解析HTTP请求(协议解析模块),规则检测(规则模块),做不同的防御动作(动作模块),并将防御过程(日志模块)记录下来。所以本文中的WAF的实现由五个模块(配置模块、协议解析模块、规则模块、动作模块、错误处理模块)组成。
以下方案选择其中之一即可:
1 Yum安装OpenResty(推荐)
源码安装和Yum安装选择其一即可,默认均安装在/usr/local/openresty目录下。
[root@opsany ~]# wget https://openresty.org/package/centos/openresty.repo
[root@opsany ~]# sudo mv openresty.repo /etc/yum.repos.d/
[root@opsany ~]# sudo yum install -y openresty
[root@opsany ~]# vim /usr/local/openresty/nginx/conf/nginx.conf
#在默认的server配置中增加
location /hello {
default_type text/html;
content_by_lua_block {
ngx.say("<p>hello, world</p>")
}
}
[root@opsany ~]# /usr/local/openresty/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/openresty-1.17.8.2/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/openresty-1.17.8.2/nginx/conf/nginx.conf test is successful
[root@opsany ~]# /usr/local/openresty/nginx/sbin/nginx
[root@opsany ~]# curl http://127.0.0.1/hello
<p>hello, world</p>
[root@opsany ~]# git clone https://github.com/unixhot/waf.git
[root@opsany ~]# cp -r ./waf/waf /usr/local/openresty/nginx/conf/
[root@opsany ~]# vim /usr/local/openresty/nginx/conf/nginx.conf
#在http{}中增加,注意路径,同时WAF日志默认存放在/tmp/日期_waf.log
#WAF
lua_shared_dict limit 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@opsany ~]# ln -s /usr/local/openresty/lualib/resty/ /usr/local/openresty/nginx/conf/waf/resty
[root@opsany ~]# /usr/local/openresty/nginx/sbin/nginx -t
[root@opsany ~]# /usr/local/openresty/nginx/sbin/nginx -s reload
[root@nginx-lua ~]# cd /usr/local/src
[root@nginx-lua src]# wget http://nginx.org/download/nginx-1.12.1.tar.gz
[root@nginx-lua src]# wget https://nchc.dl.sourceforge.net/project/pcre/pcre/8.41/pcre-8.41.tar.gz
#其次,下载当前最新的luajit和ngx_devel_kit (NDK),以及春哥(章)编写的lua-nginx-module
[root@nginx-lua src]# wget http://luajit.org/download/LuaJIT-2.0.5.tar.gz
[root@nginx-lua src]# wget https://github.com/simpl/ngx_devel_kit/archive/v0.3.0.tar.gz
[root@nginx-lua src]# wget wget https://github.com/chaoslawful/lua-nginx-module/archive/v0.10.10.zip
[root@nginx-lua src]# useradd -s /sbin/nologin -M www
[root@openstack-compute-node5 src]# tar zxvf v0.3.0.tar.gz
[root@openstack-compute-node5 src]# unzip -q v0.10.10.zip
[root@webs-ebt src]# tar zxvf LuaJIT-2.0.5.tar.gz
[root@webs-ebt src]# cd LuaJIT-2.0.5
[root@webs-ebt LuaJIT-2.0.5]# make && make install
[root@webs-ebt src]# tar zxf nginx-1.12.1.tar.gz
[root@webs-ebt src]# tar zxvf pcre-8.41.tar.gz
[root@webs-ebt src]# cd nginx-1.12.1
[root@webs-ebt nginx-1.12.1]# export LUAJIT_LIB=/usr/local/lib
[root@webs-ebt nginx-1.12.1]# export LUAJIT_INC=/usr/local/include/luajit-2.0
[root@webs-ebt nginx-1.12.1]#./configure --user=www --group=www --prefix=/usr/local/nginx-1.12.1/ --with-pcre=/usr/local/src/pcre-8.41 --with-http_stub_status_module --with-http_sub_module --with-http_gzip_static_module --without-mail_pop3_module --without-mail_imap_module --without-mail_smtp_module --add-module=../ngx_devel_kit-0.3.0/ --add-module=../lua-nginx-module-0.10.10/
[root@webs-ebt nginx-1.12.1]# make -j2 && make install
[root@webs-ebt nginx-1.12.1]# ln -s /usr/local/nginx-1.12.1 /usr/local/nginx
[root@webs-ebt nginx-1.12.1]# ln -s /usr/local/lib/libluajit-5.1.so.2 /lib64/libluajit-5.1.so.2
如果不创建符号链接,可能出现以下异常:
error while loading shared libraries: libluajit-5.1.so.2: cannot open shared object file: No such file or directory
安装完毕后,下面可以测试安装了,修改nginx.conf 增加第一个配置。
location /hello {
default_type 'text/plain';
content_by_lua 'ngx.say("hello,lua")';
}
[root@webs-ebt src]# /usr/local/nginx/sbin/nginx -t
[root@webs-ebt src]# /usr/local/nginx/sbin/nginx -t
然后访问http://xxx.xxx.xxx.xxx/hello 如果出现hello,lua。表示安装完成,然后就可以。
[root@opsany ~]# yum install -y readline-devel pcre-devel openssl-devel
2.1 下载并编译安装OpenResty
[root@opsany ~]# cd /usr/local/src
[root@opsany src]# wget https://openresty.org/download/openresty-1.17.8.2.tar.gz
[root@opsany src]# tar zxf openresty-1.17.8.2.tar.gz
[root@opsany src]# cd openresty-1.17.8.2
[root@opsany openresty-1.17.8.2]# ./configure --prefix=/usr/local/openresty-1.17.8.2 \
--with-luajit --with-http_stub_status_module \
--with-pcre --with-pcre-jit \
--with-file-aio --with-threads
[root@opsany openresty-1.17.8.2]# gmake && gmake install
[root@opsany openresty-1.17.8.2]# cd
[root@opsany ~]# ln -s /usr/local/openresty-1.17.8.2/ /usr/local/openresty
http {
include mime.types;
default_type application/octet-stream;
\#waf
lua_shared_dict limit 50m;
<font color=red> lua_package_path "/usr/local/openresty/nginx/conf/waf/?.lua";</font>
<font color=red> init_by_lua_file "/usr/local/openresty/nginx/conf/waf/init.lua";</font>
<font color=red> access_by_lua_file "/usr/local/openresty/nginx/conf/waf/access.lua";</font>
sendfile on;
\#tcp_nopush on;
\#keepalive_timeout 0;
keepalive_timeout 65;
\#gzip on;
server {
listen 80;
server_name localhost;
\#charset koi8-r;
\#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
location /hello {
default_type text/html;
content_by_lua_block {
ngx.say("<p>hello zhoucheng!</p>")
}
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
location /test {
access_by_lua_block {
local redis = require 'resty.redis';
local red = redis:new();
red:set_timeouts(1000, 1000, 1000);
local ok, err = red:connect("127.0.0.1", 6379);
if not ok then
ngx.log(ngx.ERR, "failed to connect: ", err)
return
end
local remote_addr = ngx.var.remote_addr;
ngx.log(ngx.ERR, "remote_addr ==> ", remote_addr);
ngx.log(ngx.ERR, "red:sismember('balck-list', remote_addr)");
res, err = red:sismember('black-list', remote_addr);
ngx.log(ngx.ERR, "是否在黑名单 ==> ", res);
if res == 1 then
ngx.log(ngx.ERR, "输出 403");
ngx.exit(ngx.HTTP_FORBIDDEN);
else
ngx.log(ngx.ERR, "输出 200");
ngx.exit(ngx.OK);
end
}
echo "test";
content_by_lua_block {
ngx.say("test");
}
}
location /add {
content_by_lua_block {
local redis = require 'resty.redis';
local red = redis:new();
red:set_timeouts(1000, 1000, 1000);
local ok, err = red:connect("127.0.0.1", 6379);
if not ok then
ngx.say("failed to connect: ", err)
return
end
local members = red:smembers("black-list");
if members then
ngx.say("添加前黑名单 ==> ", members);
end
local ip = ngx.var.arg_ip;
ngx.say("red:sadd('balck-list', ip)");
local res, err = red:sadd('black-list', ip);
if not res then
ngx.say("failed to sadd: ", err)
return
end
local members = red:smembers("black-list");
if members then
ngx.say("添加后黑名单 ==> ", members);
end
}
}
location /delete {
content_by_lua_block {
local redis = require 'resty.redis';
local red = redis:new();
red:set_timeouts(1000, 1000, 1000);
local ok, err = red:connect("127.0.0.1", 6379);
if not ok then
ngx.say("failed to connect: ", err)
return
end
local members = red:smembers("black-list");
if members then
ngx.say("删除前黑名单 ==> ", members);
end
local ip = ngx.var.arg_ip;
ngx.say("red:srem('balck-list', ip)");
local res, err = red:srem('black-list', ip);
if not res then
ngx.say("failed to srem: ", err)
return
end
local members = red:smembers("black-list");
if members then
ngx.say("删除后黑名单 ==> ", members);
end
}
}
}
}
# 初始黑名单为空
huli@hudeMacBook-Pro black % curl localhost:6001/test
test
# 添加黑名单
huli@hudeMacBook-Pro black % curl --location --request GET 'localhost:6001/add?ip=172.18.0.1'
添加前黑名单 ==> gtlx
red:sadd('balck-list', ip)
添加后黑名单 ==> 172.18.0.1gtlx
huli@hudeMacBook-Pro black % curl localhost:6001/test
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>openresty/1.21.4.1</center>
</body>
</html>
# 删除黑名单
huli@hudeMacBook-Pro black % curl --location --request GET 'localhost:6001/delete?ip=172.18.0.1'
删除前黑名单 ==> 172.18.0.1gtlx
red:srem('balck-list', ip)
删除后黑名单 ==> gtlx
huli@hudeMacBook-Pro black % curl localhost:6001/test
test
1.下载waf,https://github.com/unixhot/waf 下载后的目录如下:
2、将waf文件夹下的4个lua文件拷贝到lualib文件夹下
3、然后将waf文件夹也拷贝到lualib文件夹中,改名为waf_conf,waf_conf文件夹中只有一个rule-config文件夹
4、在nginx.conf文件的http标签中增加配置。配置waf需要的lua文件,在HTTP下面增加两个代码
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
lua_shared_dict limit 10m;
lua_package_path "C:/Users/Administrator/Desktop/openresty-1.21.4.1-win64/lualib/?.lua";
init_by_lua_file C:/Users/Administrator/Desktop/openresty-1.21.4.1-win64/lualib/init.lua;
access_by_lua_file C:/Users/Administrator/Desktop/openresty-1.21.4.1-win64/lualib/access.lua;
#....后面部分省略
5、在启动过程中如果报错,就是连因为role-config中没有配置
## **在Nginx文件中增加了lua扩展模块,启动时遇到的问题**
2021/09/01 09:06:12 [error] 59260#59260: *3 failed to load external Lua file "/root/openresty/application/nginx/conf/waf/access.lua": cannot open /root/openres
ty/application/nginx/conf/waf/access.lua: Permission denied, client: 192.168.56.1, server: localhost, request: "GET /hello HTTP/1.1", host: "192.168.56.7"
2021/09/01 09:11:20 [error] 61312#61312: *1 lua entry thread aborted: runtime error: /root/openresty/application/nginx/conf/waf/init.lua:152: bad argument #1 t
o 'pairs' (table expected, got nil)
stack traceback:
coroutine 0:
[C]: in function 'pairs'
/root/openresty/application/nginx/conf/waf/init.lua:152: in function 'user_agent_attack_check'
/root/openresty/application/nginx/conf/waf/access.lua:6: in function 'waf_main'
/root/openresty/application/nginx/conf/waf/access.lua:18: in main chunk, client: 192.168.56.1, server: localhost, request: "GET /hello HTTP/1.1", host:
"192.168.56.7"
# 解决办法:修改/waf/config.lua文件中的规则文件夹参数
--rule setting
config_rule_dir = "/root/openresty/application/nginx/conf/waf/rule-config"
6、配置lualib夹下的的config.lua文件,设置rule-config地址
--WAF config file,enable = "on",disable = "off"
--waf status
config_waf_enable = "on"
--log dir
config_log_dir = "/tmp"
--rule setting
config_rule_dir = "C:/Users/Administrator/Desktop/openresty-1.21.4.1-win64/lualib/waf_conf/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
config_cc_rate = "10/60"
--enable/disable post filtering
config_post_check = "on"
--config waf output redirect/html
config_waf_output = "html"
--if config_waf_output ,setting url
config_waf_redirect_url = "https://www.unixhot.com"
config_output_html=[[
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="zh-cn" />
<title>OpsAny|Web应用防火墙</title>
</head>
<body>
<h1 align="center"> 欢迎白帽子进行授权安全测试,安全漏洞请联系QQ:57459267
</body>
</html>
]]
7黑白名单的IP在block和white添加或者修改即可。