一、实现场景
OpenResty是一个基于nginx的Web应用服务器,它支持Lua脚本语言和各种第三方模块,可以实现高性能、可扩展、易开发的Web应用程序。与此同时,OpenResty也可以通过结合WAF(Web应用防火墙)来保护网站安全。
- 通过OpenResty提供高性能的Web服务,并使用WAF对进出流量进行实时监控和分析,保护Web应用程序的安全。
- 使用OpenResty的Lua脚本语言和第三方模块,对WAF进行自定义配置和扩展,以适应不同的业务需求和安全策略。
二、WAF介绍
WAF(Web Application Firewall),也叫Web应用程序防火墙,是一种专门用于保护Web应用程序免受恶意攻击的安全解决方案。WAF可以在Web应用程序和客户端之间进行拦截和过滤,以检测并防止常见的Web攻击类型(如SQL注入、跨站脚本等)。
WAF可以通过多种方式进行检测和拦截,包括基于规则、行为、机器学习等技术。当WAF检测到异常流量或恶意攻击时,可以根据预设的规则或策略,执行相应的动作,如拦截、禁止访问、重定向等。
WAF通常被部署在Web应用程序的前端,拦截和分析所有进出流量,从而提供更全面的安全保护。它可以与其他安全组件(如IDS、IPS等)结合使用,形成完整的Web安全防御体系。
WAF可以通过以下方式保护Web应用程序:
- 阻止基于漏洞的攻击,如SQL注入、跨站点脚本(XSS)、跨站点请求伪造(CSRF)等。
- 防范恶意爬虫、扫描器、爆破工具等自动化攻击工具。
- 保护Web应用程序的敏感信息,如身份证号、银行卡号等。
- 阻止DDoS攻击。
WAF可以部署在云上或本地,可以作为硬件设备或软件实现,也可以作为一种云服务提供。
三、WAF部署
开源WAF功能
- 项目地址:https://github.com/unixhot/waf
- 提示说明:该项目已经很久没人维护了,但基本功能还是可以正常使用。
- 支持IP白名单和黑名单功能,直接将黑名单的IP访问拒绝。
- 支持URL白名单,将不需要过滤的URL进行定义。
- 支持User-Agent的过滤,匹配自定义规则中的条目,然后进行处理(返回403)。
- 支持CC攻击防护,单个URL指定时间的访问次数,超过设定值,直接返回403。
- 支持Cookie过滤,匹配自定义规则中的条目,然后进行处理(返回403)。
- 支持URL过滤,匹配自定义规则中的条目,如果用户请求的URL包含这些,返回403。
- 支持URL参数过滤,原理同上。
- 支持日志记录,将所有拒绝的操作,记录到日志中去。
- 日志记录为JSON格式,便于日志分析,例如使用ELK进行攻击日志收集、存储、搜索和展示。
开源WAF部署
1、测试Lua
- 因为我们是基于Lua脚本实现防CC的,那么就需要先测试OpenResty是否可以正常返回Lua
[root@dqzboy ~]# vim /usr/local/openresty/nginx/conf/conf.d/default.conf
#在server配置中增加如下规则
location /hello {
default_type text/html;
content_by_lua_block {
ngx.say("<p>hello, world</p>")
}
}
- 检查语法是否正确,没问题则访问测试
[root@dqzboy ~]# openresty -t
[root@dqzboy ~]# openresty -s reload

- 返回正常,那么我们就可以把上面添加的
location规则配置删除了
2、安装部署
2.1:下载WAF
[root@dqzboy ~]# git clone https://github.com/unixhot/waf.git
[root@dqzboy ~]# cp -a ./waf/waf /usr/local/openresty/nginx/conf/
2.2:WAF结构
2.2.1:WAF内目录和文件说明
access.lua、init.lua、lib.lubwaf功能实现lua代码config.lua配置文件rule-config防御规则文件存储目录
rule-config目录内文件说明:
args.rule异常get请求参数策略文件blackip.ruleIP黑名单策略文件cookie.ruleCookie策略文件post.rule异常post请求参数策略文件url.rule异常url策略文件useragent.rule异常useragent策略文件whiteip.ruleIP白名单策略文件whiteurl.ruleURL白名单策略文件
2.2.2:获取客户端真实IP方法
原代码获取客户端真实IP,如果经过多个代理节点传过来的X_Forwarded_For的IP值不止一个的时候会有问题
此功能函数定义的脚本位置:
/usr/local/openresty/nginx/conf/waf/lib.lua
- 部分客户端访问经过多个代理节点之后,
X_Forwarded_For获得的IP地址可能不止一个,我们只取第一个ip地址即为客户端真实IP地址
function get_client_ip()
loacl 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

2.2.3:WAF主要配置文件说明
- 配置文件
config.lua参数详细解释
--WAF config file,enable = "on",disable = "off"
--waf 开启与关闭
config_waf_enable = "on"
--log 日志目录;注意openresty运行用户需要有对应目录的权限,不然无法输出日志
config_log_dir = "/usr/local/openresty/nginx/logs/"
--WAF规则文件存储路径
config_rule_dir = "/usr/local/openresty/nginx/conf/waf/rule-config"
--是否开启 URL 白名单
config_white_url_check = "on"
--是否开启ip 白名单
config_white_ip_check = "on"
--是否开启ip 黑名单
config_black_ip_check = "on"
--启用/禁用URL过滤
config_url_check = "on"
--启用/禁用URL参数筛选
config_url_args_check = "on"
--启用/禁用用户代理筛选
config_user_agent_check = "on"
--是否拦截 cookie 攻击
config_cookie_check = "on"
--是否开启拦截 cc 攻击
config_cc_check = "on"
--设置cc攻击频率,单位为秒, 这里表示1分钟同一个IP只能请求同一个url地址20次,超过20次返回403;默认示例中为单个IP地址在60秒内访问同一个页面次数超过10次则认为是cc攻击,则自动禁止此IP地址访问此页面60秒,60秒后解封(封禁过程中此IP地址依然可以访问其它页面,如果同一个页面访问次数超过10次依然会被禁止)
config_cc_rate = "20/60"
--是否拦截 post 攻击
config_post_check = "on"
--对于违反规则的请求则跳转到一个自定义html页面还是指定页面,值为 html 和 redirect
config_waf_output = "html"
--指定违反请求后跳转的指定html页面
config_waf_redirect_url = "https://www.dqzboy.com"
config_output_html=[[
--指定违反规则后跳转的自定义html页面,页面输出警告内容,可在中括号内自定义;注意修改默认配置文件里的内容
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>浅时光云WAF防火墙</title>
<link rel="icon" href="https://www.dqzboy.com/images/logo.png" type="image/x-icon"/>
<style type="text/css">
/* 背景图片 */
body {
background: url(https://www.dqzboy.com/images/waf.jpg);
background-size: cover;
}
/* 外边框 */
.container {
margin: 100px auto;
width: 500px;
height: 700px;
border-radius: 10px;
border: 3px solid #FFF;
background-color: rgba(255,255,255,0.8);
box-shadow: 0 0 20px #000;
padding: 20px;
text-align: center;
font-family: Arial, sans-serif;
}
/* 标题 */
.title {
font-size: 30px;
font-weight: bold;
margin-bottom: 20px;
color: #333;
text-shadow: 2px 2px #FFF;
}
/* 提示信息 */
.info {
margin-bottom: 30px;
font-size: 18px;
font-weight: bold;
color: #333;
text-shadow: 1px 1px #FFF;
}
/* 按钮 */
.button {
display: inline-block;
margin-top: 30px;
padding: 10px 30px;
border-radius: 5px;
border: none;
background-color: #F44336;
color: #FFF;
font-size: 20px;
font-weight: bold;
cursor: pointer;
text-shadow: 1px 1px #000;
}
/* 按钮悬停效果 */
.button:hover {
background-color: #D32F2F;
}
/* 客户端信息 */
.client-info {
margin-top: 50px;
font-size: 20px;
font-weight: bold;
color: #333;
text-shadow: 1px 1px #FFF;
}
/* 客户端IP */
.client-ip {
margin-top: 20px;
font-size: 16px;
color: #333;
text-shadow: 1px 1px #FFF;
}
/* 客户端地区 */
.client-region {
margin-top: 10px;
font-size: 16px;
color: #333;
text-shadow: 1px 1px #FFF;
}
</style>
</head>
<body>
<div class="container">
<div class="title">浅时光博客WAF防火墙</div>
<div class="info">
<p>抱歉,您的访问被拦截。</p>
<p>我们检测到您的请求可能存在安全威胁。</p>
</div>
<button class="button" onclick="location.href='https://www.dqzboy.com'">返回主页</button>
<div class="ip-container">
<h2>请求信息</h2>
<p>请求IP地址: <span id="ip-address">正在获取IP地址...</span></p>
<p>请求地理位置: <span id="ip-location">正在获取地理位置...</span></p>
</div>
<script>
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.ipify.org?format=json", true);
xhr.onload = function () {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
var ip = data.ip;
document.getElementById("ip-address").innerHTML = "IP: " + ip;
// Get client location using IP
var xhr2 = new XMLHttpRequest();
xhr2.open("GET", "https://ipapi.co/" + ip + "/json/", true);
xhr2.onload = function () {
if (xhr2.status === 200) {
var locationData = JSON.parse(xhr2.responseText);
var region = locationData.region;
var city = locationData.city;
var country = locationData.country_name;
document.getElementById("ip-location").innerHTML = city + ", " + region + ", " + country;
} else {
console.error(xhr2.statusText);
}
};
xhr2.onerror = function () {
console.error(xhr2.statusText);
};
xhr2.send();
} else {
console.error(xhr.statusText);
}
};
xhr.onerror = function () {
console.error(xhr.statusText);
};
xhr.send();
</script>
<br>
<br>
<br>
<br>
<br>
<br>
<div>
<p> 浅时光博客 | 精彩程序人生</p>
</div>
</div>
</body>
</html>
]]
2.3:调用Lua
- 在
nginx.conf的 http 段添加引入lua脚本文章来源(Source):https://dqzboy.com 配置,同时WAF日志默认存放在/tmp/日期_waf.log
[root@dqzboy ~]# vim /usr/local/openresty/nginx/conf/nginx.conf
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";

- 配置软链接,指向lua库文件
[root@dqzboy ~]# ln -s /usr/local/openresty/lualib/resty/ /usr/local/openresty/nginx/conf/waf/resty
- 检查nginx配置,没有问题则重载nginx服务
[root@dqzboy ~]# /usr/local/openresty/nginx/sbin/nginx -t
[root@dqzboy ~]# /usr/local/openresty/nginx/sbin/nginx -s reload
2.4:测试验证
- 部署完毕可以尝试url规则文件里面定义的拦截URl路径进行测试。
- 例如:https://www.xxxx.com/.inc

四、报错总结
错误提示:
[warn] 4042011#4042011: *961242 [lua] _G write guard:12: __newindex(): writing a global Lua variable ('CLIENT_IP') which may lead to race conditions between concurrent requests, so prefer the use of 'local' variables stack traceback:
解决方法:
- 修改
lib.lua代码, CLIENT_IP 加个local 修饰
function get_client_ip()
-- 添加local 解决CLIENT_IP变量报错问题
localCLIENT_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

必须 注册 为本站用户, 登录 后才可以发表评论!