Openrestry(Nginx+Lua)之WAF实现
<div class="page-date">
<span id="busuanzi_container_page_pv"> | 本文阅读量:<span id="busuanzi_value_page_pv"></span>次</span>
</div>
</div>
<h1 id="openrestry简介">openrestry简介:</h1>
-
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 等都进行一致的高性能响应。
openrestry安装部署:
- 部署文档参考资料
https://openresty.org/cn/installation.html https://openresty.org/cn/linux-packages.html
我是在CentOS发行版下通过yum方式安装的,图个简单,毕竟openrestry的部署不是我们的重点。
openrestry的WAF功能列表:
- 支持IP白名单和黑名单功能,直接将黑名单的IP访问拒绝。
- 支持URL白名单,将不需要过滤的URL进行定义。
- 支持User-Agent的过滤,匹配自定义规则中的条目,然后进行处理(返回403)。
- 支持CC攻击防护,单个URL指定时间的访问次数,超过设定值,直接返回403。
- 支持Cookie过滤,匹配自定义规则中的条目,然后进行处理(返回403)。
- 支持URL过滤,匹配自定义规则中的条目,如果用户请求的URL包含这些,返回403。
- 支持URL参数过滤,原理同上。
- 支持日志记录,将所有拒绝的操作,记录到日志中去。
- 日志记录为JSON格式,便于日志分析,例如使用ELKStack进行攻击日志收集、存储、搜索和展示。
WAF实现基本原理:
WAF实现 WAF一句话描述,就是解析HTTP请求(协议解析模块),规则检测(规则模块),做不同的防御动作(动作模块),并将防御过程(日志模块)记录下来。所以本文中的WAF的实现由五个模块(配置模块、协议解析模块、规则模块、动作模块、错误处理模块)组成。
openrestry启用WAF配置:
参考资料,感谢这两个项目人员的贡献
https://github.com/loveshell/ngx_lua_waf https://github.com/unixhot/waf
- 获取WAF实现的lua代码,并放到openrestry配置文件存放路径:
PS: yum方式安装openrestry后,程序会在部署在:/usr/local/openresty路径。
#git clone https://github.com/unixhot/waf.git
#cp -a ./waf/waf /usr/local/openresty/nginx/conf/
- 修改Nginx的配置文件,加入以下配置。在nginx.conf的http段添加:
#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";
- 根据需要修改WAF配置参数:
一般需要修改WAF日志默认存放路径/tmp/,其他配置项参数的修改参考注解即可,被拦截后的提醒页面内容根据需要自行定义:
- 验证配置和启动openrestry服务:
#/usr/local/openresty/nginx/sbin/nginx –t
#/usr/local/openresty/nginx/sbin/nginx
nginx配置参考:
worker_processes 4;
events {
worker_connections 1024;
}
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;
keepalive_timeout 65;
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;
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
include vhosts/*.conf;
}
关于lua脚本获取客户端真实IP方法调整
原代码获取客户端真实IP,如果经过多个代理节点传过来的X_Forwarded_For的IP值不止一个的时候会有问题。
1. 此功能函数定义的脚本位置:
/usr/local/openresty/nginx/conf/waf/lib.lua
2. 此功能函数参考:
方法一:
- 原代码:
--Get the client IP
function get_client_ip()
CLIENT_IP = ngx.req.get_headers()["X_real_ip"]
if CLIENT_IP == nil then
CLIENT_IP = ngx.req.get_headers()["X_Forwarded_For"]
end
if CLIENT_IP == nil then
CLIENT_IP = ngx.var.remote_addr
end
if CLIENT_IP == nil then
CLIENT_IP = "unknown"
end
return CLIENT_IP
end
方法二:
部分客户端访问经过多个代理节点之后,X_Forwarded_For获得的IP地址可能不止一个,我们只取第一个ip地址即为客户端真实IP地址,比如如下日志记录:
100.116.224.220 - - [29/Nov/2018:09:14:37 +0800-1543454077.462] "POST /api/operation/abc HTTP/1.1" 200 873 "https://abc.com/2018031602388641/0.2.1811161506.32/index.html" "Mozilla/5.0 (Linux; U; Android 9; zh-CN; ALP-AL00 Build/HUAWEIALP-AL00)" "223.104.64.51, 11.34.31.180, 110.75.242.180"
- 调整后代码(经验证有效):
--Get the client IP
function get_client_ip()
CLIENT_IP = ngx.req.get_headers()["X_real_ip"]
if CLIENT_IP == nil then
if ngx.var.http_x_forwarded_for ~= nil then
CLIENT_IP = string.match(ngx.var.http_x_forwarded_for, "%d+.%d+.%d+.%d+", 1);
end
end
if CLIENT_IP == nil then
CLIENT_IP = ngx.var.remote_addr or '127.0.0.1'
end
if CLIENT_IP == nil then
CLIENT_IP = "unknown"
end
return CLIENT_IP
end
方法三:
- 调整后代码(验证无效)
--Get the client IP
function get_client_ip()
HEADERS = ngx.req.get_headers()
CLIENT_IP = HEADERS["X_real_ip"]
if CLIENT_IP == nil then
if type(HEADERS["x-forwarded-for"]) == "table" then
CLIENT_IP = HEADERS["x-forwarded-for"][1]
else
CLIENT_IP = HEADERS["x-forwarded-for"]
end
end
if CLIENT_IP == nil then
CLIENT_IP = ngx.var.remote_addr or '127.0.0.1'
end
if CLIENT_IP == nil then
CLIENT_IP = "unknown"
end
return CLIENT_IP
end
<div class="page-footer">
<div class="page-tag">
<span>Tags:</span>
<a href="/tags#Blog" class="tag">| Blog</a>
<a href="/tags#security" class="tag">| security</a>
<a href="/tags#web" class="tag">| web</a>
<a href="/tags#openrestry" class="tag">| openrestry</a>
</div>
<div id="gitmentContainer"></div>
<link rel="stylesheet" href="https://jjeejj.github.io/css/gitment.css"/>
<script src="https://jjeejj.github.io/js/gitment.js"></script>
<script>
var gitment = new Gitment({
owner: 'lichi6174',
repo: 'lichi6174.github.io',
oauth: {
client_id: '6a7d368a50aba47c2074',
client_secret: '21d9d8a3ffba794b0dbda3e2f3276aa9ddd98ec5',
},
});
gitment.render('gitmentContainer');
</script>
<section>
<div class="content-play">
<p><a href="javascript:void(0)" onclick="dashangToggle()" class="dashang" title="打赏,支持一下">打赏一下呗</a></p>
<div class="hide_box-play"></div>
<div class="shang_box-play">
<a class="shang_close-play" href="javascript:void(0)" onclick="dashangToggle()" title="关闭"><img src="https://lichi6174.github.io//assets/img/close.jpg" alt="取消"/></a>
<div class="shang_tit-play">
<p>对你有帮助,那就打赏一下吧</p>
</div>
<div class="shang_payimg">
<img src="https://lichi6174.github.io//assets/img/wechatpayimg.png" alt="扫码支持" title="扫一扫"/>
</div>
<div class="pay_item" data-id="weipay">
<span class="pay_logo"><img src="https://lichi6174.github.io//assets/img/wechat.jpg" alt="微信"/></span>
</div>
<div class="shang_payimg">
<img src="https://lichi6174.github.io//assets/img/alipayimg.jpg" alt="扫码支持" title="扫一扫"/>
</div>
<div class="pay_item checked" data-id="alipay">
<span class="pay_logo"><img src="https://lichi6174.github.io//assets/img/alipay.jpg" alt="支付宝"/></span>
</div>
<div class="pay_explain">扫码打赏,金额随意</div>
</div>
</div>
<script type="text/javascript">
function dashangToggle(){
$(".hide_box-play").fadeToggle();
$(".shang_box-play").fadeToggle();
}
</script>
<div style="text-align:center;margin:50px 0; font:normal 14px/24px 'MicroSoft YaHei';"></div>
<style type="text/css">
.content-play{width:100%;margin-top: 20px;margin-bottom: 10px;height:40px;text-align:center;}
.hide_box-play{z-index:999;filter:alpha(opacity=50);background:#666;opacity: 0.5;-moz-opacity: 0.5;left:0;top:0;height:99%;width:100%;position:fixed;display:none;}
.shang_box-play{width:100%;height:540px;padding:10px;background-color:#fff;border-radius:10px;position:fixed;z-index:1000;left:0%;top:50%;margin-left:0px;margin-top:-280px;border:1px dotted #dedede;display:none;}
.shang_box-play img{border:none;border-width:0;}
.dashang{width:100px;margin:5px auto;height:25px;line-height:25px;padding:10px;background-color:#E74851;color:#fff;text-align:center;text-decoration:none;border-radius:10px;font-weight:bold;font-size:16px;transition: all 0.3s;}
.dashang:hover{opacity:0.8;padding:15px;font-size:18px;}
.shang_close-play{float:right;display:inline-block;
margin-right: 10px;margin-top: 20px;
}
.shang_logo{display:block;text-align:center;margin:20px auto;}
.shang_tit-play{width: 100%;height: 75px;text-align: center;line-height: 66px;color: #a3a3a3;font-size: 16px;background: url('/images/payimg/cy-reward-title-bg.jpg');font-family: 'Microsoft YaHei';margin-top: 7px;margin-right:2px;}
.shang_tit-play p{color:#a3a3a3;text-align:center;font-size:16px;}
.shang_payimg{padding:10px;/*border:6px solid #EA5F00;**/margin:0 auto;border-radius:3px;height:140px;display:inline-block;}
.shang_payimg img{display:inline-block;margin-right:10px;float:left;text-align:center;width:140px;height:140px; }
.pay_explain{text-align:center;margin:10px auto;font-size:12px;color:#545454;}
.shang_payselect{text-align:center;margin:0 auto;margin-top:40px;cursor:pointer;height:60px;width:500px;margin-left:110px;}
.shang_payselect .pay_item{display:inline-block;margin-right:140px;float:left;}
.shang_info-play{clear:both;}
.shang_info-play p,.shang_info-play a{color:#C3C3C3;text-align:center;font-size:12px;text-decoration:none;line-height:2em;}
</style>
</div>
</div>
</div>