在ubuntu上使用Openresty+lua实现WAF—-折腾笔记

作者: 康康 分类: Web安全,安全运维 发布时间: 2018-01-04 22:53

声明:参考读懂了loveshell的waf实现思路,重新改写了实现的lua方法,扩充了其规则库,改为在常用的ubuntu上部署。

1.本WAF的功能

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

2.WAF的特点

2.1异常检测协议

Web应用防火墙会对HTTP的请求进行异常检测,拒绝不符合HTTP标准的请求。从而减少攻击的影响范围。

2.2增强的输入验证

增强输入验证,可以有效防止网页篡改、信息泄露、木马植入等恶意网络入侵行为。从而减小Web服务器被攻击的可能性。

2.3及时补丁

修补Web安全漏洞,是Web应用开发者最头痛的问题,没人会知道下一秒有什么样的漏洞出现,会为Web应用带来什么样的危害。WAF可以为我们做这项工作了——只要有全面的漏洞信息WAF能在不到一个小时的时间内屏蔽掉这个漏洞。当然,这种屏蔽掉漏洞的方式不是非常完美的,并且没有安装对应的补丁本身就是一种安全威胁,但我们在没有选择的情况下,任何保护措施都比没有保护措施更好。

2.4基于规则的保护和基于异常的保护

基于规则的保护可以提供各种Web应用的安全规则,WAF生产商会维护这个规则库,并时时为其更新。用户可以按照这些规则对应用进行全方面检测。还有的产品可以基于合法应用数据建立模型,并以此为依据判断应用数据的异常。但这需要对用户企业的应用具有十分透彻的了解才可能做到,可现实中这是十分困难的一件事情。

2.5状态管理

WAF能够判断用户是否是第一次访问并且将请求重定向到默认登录页面并且记录事件。通过检测用户的整个操作行为我们可以更容易识别攻击。状态管理模式还能检测出异常事件(比如登陆失败),并且在达到极限值时进行处理。这对暴力攻击的识别和响应是十分有利的。

2.6其他防护技术

WAF还有一些安全增强的功能,可以用来解决WEB程序员过分信任输入数据带来的问题。比如:隐藏表单域保护、抗入侵规避技术、响应监视和信息泄露保护。

2.7WAF与网络防火墙的区别

网络防火墙作为访问控制设备,主要工作在OSI模型三、四层,基于IP报文进行检测。只是对端口做限制,对TCP协议做封堵。其产品设计无需理解HTTP会话,也就决定了无法理解Web应用程序语言如HTML、SQL语言。因此,它不可能对HTTP通讯进行输入验证或攻击规则分析。针对Web网站的恶意攻击绝大部分都将封装为HTTP请求,从80或443端口顺利通过防火墙检测。
一些定位比较综合、提供丰富功能的防火墙,也具备一定程度的应用层防御能力,如能根据TCP会话异常性及攻击特征阻止网络层的攻击,通过IP分拆和组合也能判断是否有攻击隐藏在多个数据包中,但从根本上说他仍然无法理解HTTP会话,难以应对如SQL注入、跨站脚本、cookie窃取、网页篡改等应用层攻击。
web应用防火墙能在应用层理解分析HTTP会话,因此能有效的防止各类应用层攻击,同时他向下兼容,具备网络防火墙的功能。

 

3.使用nginx配置简单实现403和404
3.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@ubuntu ~] wget http://192.168.123.166
--2017-06-05 17:14:53-- http://192.168.123.166/
Connecting to 192.168.123.166:80... connected.
HTTP request sent, awaiting response... 403 Forbidden
2017-06-05 17:14:53 ERROR 403: Forbidden.

3.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
/user/local/nginx/sbin/nginx –t
/user/local/nginx/sbin/nginx –s reload

浏览器访问带.sql
通过浏览器访问结果如下,404已生效

4.深入实现WAF

4.1 WAF实现规划

分析步骤如下:解析HTTP请求==》匹配规则==》防御动作==》记录日志
具体实现如下:

解析http请求:协议解析模块,检查useragent,检查cookie,检查url和参数,检查post
匹配规则:规则检测模块,匹配规则库(正则表达式)
防御动作:return 403 或者跳转到自定义界面
日志记录:记录到log文件中

4.2实现思路

由于nginx配置文件书写不方便,并且实现白名单功能很复杂,nginx的白名单也不适用于CC攻击,所以在这里使用nginx+lua来实现WAF,如果想使用lua,须在编译nginx的时候配置上lua,或者结合OpenResty使用,此方法不需要编译nginx时候指定lua

ngx_lua的原理:

         ngx_lua将Lua嵌入Nginx,可以让Nginx执行Lua脚本,并且高并发、非阻塞的处理各种请求。Lua内建协程,这样就可以很好的将异步回调转换成顺序调用的形式。ngx_lua在Lua中进行的IO操作都会委托给Nginx的事件模型,从而实现非阻塞调用。开发者可以采用串行的方式编写程 序,ngx_lua会自动的在进行阻塞的IO操作时中断,保存上下文;然后将IO操作委托给Nginx事件处理机制,在IO操作完成后,ngx_lua会 恢复上下文,程序继续执行,这些操作都是对用户程序透明的。
        每个NginxWorker进程持有一个Lua解释器或者LuaJIT实例,被这个Worker处理的所有请求共享这个实例。每个请求的Context会被Lua轻量级的协程分割,从而保证各个请求是独立的。
        ngx_lua采用“one-coroutine-per-request”的处理模型,对于每个用户请求,ngx_lua会唤醒一个协程用于执行用户代 码处理请求,当请求处理完成这个协程会被销毁。每个协程都有一个独立的全局环境(变量空间),继承于全局共享的、只读的“comman data”。所以,被用户代码注入全局空间的任何变量都不会影响其他请求的处理,并且这些变量在请求处理完成后会被释放,这样就保证所有的用户代码都运行 在一个“sandbox”(沙箱),这个沙箱与请求具有相同的生命周期。
得益于Lua协程的支持,ngx_lua在处理10000个并发请求时只需要很少的内存。根据测试,ngx_lua处理每个请求只需要2KB的内存,如果使用LuaJIT则会更少。所以ngx_lua非常适合用于实现可扩展的、高并发的服务。

以下自己使用ubuntu14.04+OpenResty1.9.3.2部署:

4.3 Openresty部署及要注意的问题

既然这里面安装openresty,之前先要彻底删除nginx,因为openresty自带nginx。


sudo apt-get--purge remove nginx
sudo apt-get autoremove
ps -ef|grep nginx
dpkg --get-selections|grep nginx 
sudo apt-get --purge remove nginx//查询上一步操作与nginx有关的软件,删掉
sudo apt-get --purge remove nginx-common
sudo apt-get --purge remove nginx-core
ps -ef |grep nginx//看一下进程,还有就删掉

安装依赖包

apt-cache serach readline
apt-get install libpcre-dev  libpcrecpp0 libpcre3   //nginx要用它做正则引擎
apt-get install libreadline-dev libreadline6-dev libtinfo-dev
apt-get install openssl libssl-dev perl make build-essential curl   
//openssl做安全引擎

下载并编译安装openresty(ngx_openresty-1.9.3.2.tar.gz和nginx1.9.4兼容,其他版本无法和nginx1.9.4兼容)为了保证顺利,采取兼容方案,但是实际运用过程中,自己原来安装的nginx需要改端口号或者卸载掉,openresty自带nginx

 cd /usr/local/src/
 wget https://openresty.org/download/ngx_openresty-1.9.3.2.tar.gz
 tar -zxf ngx_openresty-1.9.3.2.tar.gz
 cd ngx_openresty-1.9.3.2
[root@ 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@ ngx_openresty-1.9.3.2] make && make install
[root@ ngx_openresty-1.9.3.2]ln -s /usr/local/openresty-1.9.3.2/ /usr/local/openresty

测试openresty安装

//在 /usr/local/openresty/nginx/nginx.conf添加
location /hi {
default_type text/html;
content_by_lua_block{
ngx.say('hello fuckopenrastry')
}
}

测试并启动openrestry下的nginx

[root@ 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@ ngx_openresty-1.9.3.2] pkill nginx //杀死进程
[root@ ngx_openresty-1.9.3.2] /usr/local/openresty/nginx/sbin/nginx/重新启动


得了,部署成功

校准用户组和权限

这就完事了吗?当然不是,你会发现当你之前安装lnmp在Ubuntu时会有很多问题,记得及时查看错误日志,发现问题,如果出现502错误,就是php-fpm.sock有问题。如果出现503错误,是nginx.conf有问题。如果出现404问题,查看location ~ \.php$ {
root           /data/web(这里换成自己的主目录);

还有server里有没有主目录的声明,如果出现一直提示:An error occurred.PHP的PHP-FPM服务是启动成功的,nginx也是启动成功的,折腾了几次,看nginx错误日志,发现有权限错误~查看php-fpm.conf文件中相关配置,得出解决办法:

;listen.user = nobody
;listen.group = nobody

去掉前面的;号,然后将用户和用户组换成你的用户和用户组即可!,此时只是fpm修改了用户组,但此时要清楚nginx也要修改成相同的用户组:

vi /usr/local/nginx/conf/nginx.conf

开头有一个

#user nobody;

把井号删掉,nobody改为 用户名 [空格] 用户组,例如

user nginx web;

即以web组的nginx用户来运行nginx,修改完以后

/usr/local/nginx/sbin/nginx -s reload重启nginx

 

去除对上传文件大小的限制:

在自己测试攻防时,经常会出现文件上传限制问题导致waf没有拦截请求就报错,虽然属于开发问题,解决方法:

着报错原因是nginx不允许上传配置过大的文件,那么件把nginx的上传大小配置调高就好。
client_max_body_size 2m;
当中的2m修改成你需要的允许文件大小。
修改后,测试nginx配置是否正确
/usr/local/nginx/sbin/nginx -t
测试配置正确后,重启nginx使配置生效
/etc/init.d/nginx restart
注意:要是以php运行的话,这个大小client_max_body_size要和php.ini中的如下值的最大值差不多或者稍大,这样就不会因为提交数据大小不一致出现错误。
 post_max_size = 2M
upload_max_filesize = 2M  

当中的2m修改成你需要的允许文件大小。把当中的2m修改成你第一步设置的大小。

 

现在将自己的nginx.conf贴出来

user www-data www-data;
worker_processes 1;

#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;

#pid logs/nginx.pid;

events {
worker_connections 1024;
}

http {
include mime.types;
default_type application/octet-stream;
client_max_body_size 8m;
#log_format main '$remote_addr - $remote_user [$time_local]  '
# '$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;
#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;
server {
listen 80;
server_name localhost;

#charset koi8-r;

#access_log logs/host.access.log main;

location / {
root /usr/share/nginx/html;
index index.html index.htm index.php;
}

location /hi {
default_type text/html;
content_by_lua_block{
ngx.say('hello test openrastry')
}
}

#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}

# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
location ~ \.php$ {
root /usr/share/nginx/html;
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini

# With php5-cgi alone:
#fastcgi_pass 127.0.0.1:9000;
# With php5-fpm:
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}

# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}

# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;

# location / {
# root html;
# index index.html index.htm;
# }
#}

# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;

# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;

# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;

# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;

# location / {
# root html;
# index index.html index.htm;
# }
#}

}

4.4WAF部署

在github上克隆下代码

[root@ ~] git clone https://github.com/aviraonepiece/waf
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@ ~]# cp -a ./waf/waf /usr/local/openresty/nginx/conf/

修改Nginx的配置文件,加入(http字段)以下配置。注意路径,同时WAF日志默认存放在/tmp/日期_waf.log

[root@ ~] 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; //waf规则
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@ ~] /usr/local/openresty/nginx/sbin/nginx -t
[root@ ~]/usr/local/openresty/nginx/sbin/nginx -s reload  //重新载入更新配置

 

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

[root@ ~] mkdir /tmp/waf_logs
[root@ ~] chown -R www.www /tmp/waf_logs 
//这里要给此文件夹存放权限,可以暂时使用777权限,原则上要使用nginx的用户组

 

5.学习模块

5.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>
]]

5.2学习process.lua的配置

require ‘fuctions’
function waf_main()
ifwhite_ip_check() then–先检查是不是IP白名单,不是就给后面处理
elseifblack_ip_check() then–再检查是不是黑名单,不是就给后面处理
elseifcc_attack_check() then–判断是不是cc攻击,不是就给后面处理
elseifuser_agent_attack_check() then–判断UA有没有不干净的东西,没有就给后面处理
elseifcookie_attack_check() then–判断cookie有没有不干净的,没有就给后面处理
elseifwhite_url_check() then–看看符不符合白名url,不符合接着匹配
elseifurl_attack_check() then–看看有没有不该让访问的目录,没有就接着匹配
elseifurl_args_attack_check() then–看看get请求的参数是不是干净的,干净的话接着匹配
elseifpost_attack_check() then–最后看看post的内容有没有恶意请求
else
return
end
end
waf_main()

顺序:先检查白名单,通过即不检测;再检查黑名单,不通过即拒绝,检查UA,UA不通过即拒绝;检查cookie;URL检查;URL参数检查,post检查;

 

 

6.启用WAF并测试

6.1模拟sql注入即url攻击

显示效果如下


日志显示如下,记录了UA,匹配规则,URL,客户端类型,攻击的类型,请求的数据

{“user_agent”:”Mozilla\/5.0 (Windows NT 10.0; WOW64; Trident\/7.0; rv:11.0) like Gecko”,”rule_tag”:”_”,”client_ip”:”192.168.199.216″,”local_time”:”2018-01-03 23:09:58″,”attack_method”:”White_IP”,”req_data”:”_”,”server_name”:”localhost”}
{“user_agent”:”Mozilla\/5.0 (Windows NT 10.0; WOW64; Trident\/7.0; rv:11.0) like Gecko”,”rule_tag”:”_”,”client_ip”:”192.168.199.216″,”local_time”:”2018-01-03 23:09:58″,”attack_method”:”White_IP”,”req_data”:”_”,”server_name”:”localhost”}
{“user_agent”:”Mozilla\/5.0 (Windows NT 10.0; WOW64; Trident\/7.0; rv:11.0) like Gecko”,”rule_tag”:”_”,”client_ip”:”192.168.199.216″,”local_time”:”2018-01-03 23:09:58″,”attack_method”:”White_IP”,”req_data”:”_”,”server_name”:”localhost”}
{“user_agent”:”Mozilla\/5.0 (Windows NT 10.0; WOW64; Trident\/7.0; rv:11.0) like Gecko”,”rule_tag”:”_”,”client_ip”:”192.168.199.216″,”local_time”:”2018-01-03 23:09:59″,”attack_method”:”White_IP”,”req_data”:”_”,”server_name”:”localhost”}
{“user_agent”:”Mozilla\/5.0 (Windows NT 10.0; WOW64; Trident\/7.0; rv:11.0) like Gecko”,”rule_tag”:”_”,”client_ip”:”192.168.199.216″,”local_time”:”2018-01-03 23:09:59″,”attack_method”:”White_IP”,”req_data”:”_”,”server_name”:”localhost”}
{“user_agent”:”Mozilla\/5.0 (Windows NT 10.0; WOW64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/55.0.2883.87 Safari\/537.36″,”rule_tag”:”_”,”client_ip”:”192.168.199.216″,”local_time”:”2018-01-03 23:10:05″,”attack_method”:”White_IP”,”req_data”:”_”,”server_name”:”localhost”}

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

[root@mini1 ~] ab -n 1000 -c 50 http://192.168.199.116/
This is ApacheBench, Version 2.3 &lt;$Revision: 655654 $&gt;
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 192.168199.116 (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)

 

6.6模拟黑白IP、url参数自测,

代码参见我的github https://github.com/aviraonepiece/waf

7.2 httpgrard防cc特效

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

8、WAF上线

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注