1. 目的

为了保护Web服务安全稳定,部署Web应用防火墙(Web Application Firewall, WAF),通过对HTTP(S)请求进行检测,识别并阻断SQL注入、跨站脚本攻击(Cross Site Scripting, XSS)、网页木马上传、命令/代码注入、文件包含、敏感文件访问、第三方应用漏洞攻击、CC(挑战黑洞)攻击、恶意爬虫扫描、跨站请求伪造等攻击。

2. 架构

(架构图)

3. 实现功能

  • 支持IP白名单和黑名单功能,直接将黑名单的IP访问拒绝。
  • 支持URL白名单,将不需要过滤的URL进行定义。
  • 支持User-Agent的过滤,匹配自定义规则中的条目,然后进行处理(返回403)。
  • 支持CC攻击防护,单个URL指定时间的访问次数,超过设定值,直接返回403。
  • 支持Cookie过滤,匹配自定义规则中的条目,然后进行处理(返回403)。
  • 支持URL过滤,匹配自定义规则中的条目,如果用户请求的URL包含这些,返回403。
  • 支持URL参数过滤,原理同上。
  • 支持日志记录,将所有拒绝的操作,记录到日志中去。
  • 日志记录为JSON格式,便于日志分析,例如使用ELKStack进行攻击日志收集、存储、搜索和展示。

4. 构建WAF的方案

  1. 开源框架openresty+nginx 实现web应用防火墙(WAF)
  1. nginx lua lua-nginx-module构建web应用防火墙(WAF)
  1. ModSecurity
  1. HiHTTPS
  1. OpenWAF
  1. FreeWAF
  2. ESAPI WAF
  3. Java WAF
  1. Naxsi
  1. X-WAF
  1. VeryNginx

5. 安装openresty

5.1. 安装依赖

yum install pcre-devel openssl-devel readline-devel gcc gcc-c++ curl wget -y
  • 1.

5.2. 下载openresty

下载地址:

wget https://github.com/openresty/openresty/releases/download/v1.15.8.2/openresty-1.15.8.2.tar.gz
  • 1.

5.3. 解压openresty

tar -zxvf openresty-1.15.8.2.tar.gz
cd openresty-1.15.8.2
  • 1.
  • 2.

5.4. 编译安装openresty

./configure --with-http_stub_status_module --with-http_ssl_module --with-pcre --with-pcre-jit --with-luajit 
gmake
gmake install
  • 1.
  • 2.
  • 3.

参数说明:

  • --with-http_stub_status_module nginx监控模块
  • --with-http_ssl_module nginx ssl模块

6. 安装WAF

6.1. 下载WAF

git clone https://github.com/unixhot/waf.git
cp -a ./waf/waf/ /usr/local/nginx/conf/
  • 1.
  • 2.

6.2. 配置WAF

vi /usr/local/nginx/conf/nginx.conf
  • 1.

nginx.confhttp字段内添加以下内容:

#WAF
lua_shared_dict limit 50m; #防cc使用字典,大小50M
lua_package_path "/usr/local/nginx/conf/waf/?.lua";
init_by_lua_file "/usr/local/nginx/conf/waf/init.lua";
access_by_lua_file "/usr/local/nginx/conf/waf/access.lua";
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

根据日志记录位置,创建日志目录:

mkdir /tmp/waf_logs
chown -R nginx.nginx /tmp/waf_logs
  • 1.
  • 2.

注意:日志目录权限与nginx启动权限相同

WAF上生产之前,建议不要直接上生产,而是先记录日志,不做任何动作。确定WAF不产生误杀。

vi /usr/local/nginx/conf/waf/config.lua
  • 1.
config_log_dir = "/tmp/waf_logs" #日志记录地址
--rule setting 
config_rule_dir = "/usr/local/nginx/conf/waf/rule-config"
  • 1.
  • 2.
  • 3.

学习access.lua的配置
顺序:先检查白名单,通过即不检测;再检查黑名单,不通过即拒绝,检查UA,UA不通过即拒绝;检查cookie;URL检查;URL参数检查,post检查;

6.3. 启动WAF

/usr/local/openresty/nginx/sbin/nginx -t
/usr/local/openresty/nginx/sbin/nginx -s reload
  • 1.
  • 2.

6.4. 配置说明

--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秒内只能
--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>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.

7. 安装包

8. 安装依赖

yum install -y readline-devel pcre-devel openssl-devel gcc gcc-c++ lua-devel wget
  • 1.

9. 解压NDK和lua-nginx-module

tar zxvf lua-nginx-module-0.10.10.tar.gz
tar zxvf ngx_devel_kit-0.3.0.tar.gz
  • 1.
  • 2.

10. 安装LuaJIT

10.1. 下载LuaJIT

git clone https://github.com/openresty/luajit2
  • 1.

10.2. 编译并安装LuaJIT

make
make install
  • 1.
  • 2.

10.3. 配置环境变量

cat >/etc/profile.d/luajit.sh<<EOF
export LUAJIT_LIB=/usr/local/lib
export LUAJIT_INC=/usr/local/include/luajit-2.1
EOF
  • 1.
  • 2.
  • 3.
  • 4.

10.4. 加载环境变量

source /etc/profile
  • 1.

11. 安装lua-cjson

11.1. 下载lua-cjson

 https://www.kyne.com.au/~mark/software/download/lua-cjson-2.1.0.tar.gz

11.2. 解压

tar zxvf lua-cjson-2.1.0.tar.gz
cd lua-cjson-2.1.0
  • 1.
  • 2.

11.3. 配置lua-cjson

find / -name lua.h
  • 1.

选择其中一个修改Makefile文件

##### Build defaults #####
LUA_VERSION =       5.1
TARGET =            cjson.so
PREFIX =            /usr/local
#CFLAGS =            -g -Wall -pedantic -fno-inline
CFLAGS =            -O3 -Wall -pedantic -DNDEBUG
CJSON_CFLAGS =      -fpic
CJSON_LDFLAGS =     -shared
LUA_INCLUDE_DIR =   /root/luajit2/src
LUA_CMODULE_DIR =   $(PREFIX)/lib/lua/$(LUA_VERSION)
LUA_MODULE_DIR =    $(PREFIX)/share/lua/$(LUA_VERSION)
LUA_BIN_DIR =       $(PREFIX)/bin
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

11.4. 安装lua-cjson

make && make install
  • 1.

luaL_setfuncs错解决:

vi lua_cjson.c
  • 1.

static去掉static void luaL_setfuncs (lua_State *l, const luaL_Reg *reg, int nup)

make && make install
  • 1.

12. 重新编译nginx

12.1. 升级或新安装

使用新版本升级

wget http://nginx.org/download/nginx-1.16.1.tar.gz
tar zxvf nginx-1.16.1.tar.gz
cd nginx-1.16.1
  • 1.
  • 2.
  • 3.

12.2. 编译nginx

./configure --user=nginx --group=nginx --prefix=/usr/local/nginx  --with-http_stub_status_module --with-http_ssl_module --with-http_sub_module --with-http_gzip_static_module --add-module=../ngx_devel_kit-0.3.0/ --add-module=../lua-nginx-module-0.10.15/
  • 1.

编译完成了,执行make,记住,这里不要执行make install,不然会把以前安装的会覆盖的

make
  • 1.

参数说明:

  • --with-http_stub_status_module nginx监控模块
  • --with-http_ssl_module nginx ssl模块
  • --ngx_http_sub_module模块是一个过滤器,通过将一个指定的字符串替换为另一个字符串来修改响应
  • --with-http_gzip_static_module 启动预压缩功能

12.3. 创建连接

ln -s /usr/local/lib/libluajit-5.1.so.2 /lib64/libluajit-5.1.so.2
  • 1.

12.4. 验证nginx

编译完成后,会新生成一个nginx执行文件,在nginx-1.16.1/objs目录下,测试一下对应的依赖有没有装上

cd nginx-1.16.1/objs
./nginx -V
  • 1.
  • 2.

12.5. 备份nginx

复制nginx命令覆盖以前的nginx
复制前,最好把之前的nginx备份一下,以防不测

cd /usr/local/nginx/sbin/
cp nginx nginx.old
  • 1.
  • 2.

12.6. 覆盖nginx

新的覆盖,覆盖之前,最好停掉nginx

cd nginx-1.16.1/
cp objs/nginx /usr/local/nginx/sbin/
  • 1.
  • 2.

修改nginx配置在http块下设置

lua_load_resty_core off;
  • 1.

12.7. 测试nginx

先测试nginx有没有被玩坏,先检查一下

cd /usr/local/nginx
./sbin/nginx -t
./sbin/nginx
  • 1.
  • 2.
  • 3.

接下来部署WAF方法和上面一样

13. 测试WAF功能

13.1. 模拟sql注入即url攻击

http://10.8.17.32/abc.sql
  • 1.
[root@localhost waf]# cat /tmp/waf_logs/2019-12-20_waf.log
  • 1.

日志显示如下,记录了

{"user_agent":"Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/71.0.3573.0 Safari\/537.36","rule_tag":"\\.(bak|inc|old|mdb|sql|backup|java|class|tgz|gz|tar|zip)$","req_url":"\/abc.sql","client_ip":"10.8.80.104","local_time":"2019-12-20 14:37:29","attack_method":"Deny_URL","req_data":"-","server_name":"localhost"}
  • 1.

日志记录了UA、匹配规则、请求URL、客户端IP、本地时间、攻击方法、请求数据和服务器名称。

13.2. 使用ab压测工具模拟防cc攻击

yum -y install httpd-tools
ab -n 1000 -c 500 http://10.8.17.32/
  • 1.
  • 2.

13.3. 模拟ip黑名单

将请求ip放入ip黑名单中

echo "10.8.17.32" >>/usr/local/nginx/conf/waf/rule-config/blackip.rule
  • 1.

13.4. 模拟ip白名单

将请求ip放入ip白名单中,此时将不对此ip进行任何防护措施,所以sql注入时应该返回404

echo "10.8.17.32" >> /usr/local/nginx/conf/waf/rule-config/whiteip.rule
  • 1.

13.5. 模拟URL参数检测

浏览器输入http://10.8.17.32/?id=select * from name where name="jack"

详细规定在arg.rule中有规定,对请求进行了规范

14. 防cc攻击利器之httpgrard

14.1. httpgrard介绍

HttpGuard是基于openresty,以lua脚本语言开发的防cc攻击软件。而openresty是集成了高性能web服务器Nginx,以及一系列的Nginx模块,这其中最重要的,也是我们主要用到的nginx lua模块。HttpGuard基于nginx lua开发,继承了nginx高并发,高性能的特点,可以以非常小的性能损耗来防范大规模的cc攻击。

14.2. httpgrard防cc特效

  • 限制访客在一定时间内的请求次数
  • 向访客发送302转向响应头来识别恶意用户,并阻止其再次访问
  • 向访客发送带有跳转功能的js代码来识别恶意用户,并阻止其再次访问
  • 向访客发送cookie来识别恶意用户,并阻止其再次访问
  • 支持向访客发送带有验证码的页面,来进一步识别,以免误伤
  • 支持直接断开恶意访客的连接
  • 支持结合iptables来阻止恶意访客再次连接
  • 支持白名单功能
  • 支持根据统计特定端口的连接数来自动开启或关闭防cc模式

详见github地址 https://github.com/centos-bz/HttpGuard

15. WAF上线

初期上线只记录日志,不开启WAF,防止误杀。
WAF规则管理使用saltstack工具。
要知道并不是有了WAF就安全,安全在很大一部分是人为因素。

16. FAQ

16.1. FAQ1

运行报错:

nginx: [error] lua_load_resty_core failed to load the resty.core module from https://github.com/openresty/lua-resty-core; ensure you are using an OpenResty release from https://openresty.org/en/download.html (rc: 2, reason: module 'resty.core' not found:
no field package.preload['resty.core']
no file '../lua-resty-core/lib/resty/core.lua'
no file '../lua-resty-lrucache/lib/resty/core.lua'
no file './resty/core.lua'
no file '/usr/local/share/luajit-2.1.0-beta3/resty/core.lua'
no file '/usr/local/share/lua/5.1/resty/core.lua'
no file '/usr/local/share/lua/5.1/resty/core/init.lua'
no file './resty/core.so'
no file '/usr/local/lib/lua/5.1/resty/core.so'
no file '/usr/local/lib/lua/5.1/loadall.so'
no file './resty.so'
no file '/usr/local/lib/lua/5.1/resty.so'
no file '/usr/local/lib/lua/5.1/loadall.so')
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

一旦使用了lua-nginx-module就表示使用了openresty,这个resty.core是openresty的核心模块,对其下的很多函数进行了优化等。之前的一些版本默认是不编译进去的,所以需要你手动安装

下面是解决该问题的完整步骤:

  1. 下载lua-resty-core
git clone https://github.com/openresty/lua-resty-core.git
  • 1.
  1. 进入lua-resty-core目录
cd lua-resty-core
  • 1.
  1. 使用luajit编译
export LUAJIT_LIB=/path/to/luajit/lib
export LUAJIT_INC=/path/to/luajit/include/luajit-2.1
make install
  • 1.
  • 2.
  • 3.

注意将/path/to/luajit/lib和/path/to/luajit/include/luajit-2.1替换为实际的luajit库和头文件路径。

  1. 编辑nginx.conf,在http块中添加
lua_package_path "/path/to/lua-resty-core/lib/?.lua;;";
lua_package_cpath "/path/to/lua-resty-core/lib/?.lua;;";
  • 1.
  • 2.

将/path/to/lua-resty-core/lib/替换为实际的lua-resty-core安装路径。

  1. 重新加载nginx配置
nginx -s reload
  • 1.

这样应该就可以解决lua_load_resty_core失败的问题了。如果还是不行,可以检查一下luajit的路径是否正确,lua-resty-core是否安装成功等。