From 9085a708070a2fdc381c8d117ea1a24df77512dc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=A4=A7=E5=8A=9B=E4=B8=B8666?=
<147456216+DaLiWan666@users.noreply.github.com>
Date: Fri, 20 Dec 2024 22:23:46 +0800
Subject: [PATCH 1/3] =?UTF-8?q?=E7=AC=AC=E4=B8=89=E6=96=B9=E8=A7=84?=
=?UTF-8?q?=E5=88=99/=E6=8F=92=E4=BB=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
第三方实现的插件与规则
---
src/plugins/third_party/auth-plugin.lua | 165 ++++++++++++++++++
.../brute-force-login-prevention.lua | 39 +++++
.../third_party/frequent-block-detection.lua | 28 +++
.../high-frequency-error-protection.lua | 53 ++++++
src/rules/third_party/maintenance-mode.lua | 94 ++++++++++
5 files changed, 379 insertions(+)
create mode 100644 src/plugins/third_party/auth-plugin.lua
create mode 100644 src/rules/third_party/brute-force-login-prevention.lua
create mode 100644 src/rules/third_party/frequent-block-detection.lua
create mode 100644 src/rules/third_party/high-frequency-error-protection.lua
create mode 100644 src/rules/third_party/maintenance-mode.lua
diff --git a/src/plugins/third_party/auth-plugin.lua b/src/plugins/third_party/auth-plugin.lua
new file mode 100644
index 0000000..dd63902
--- /dev/null
+++ b/src/plugins/third_party/auth-plugin.lua
@@ -0,0 +1,165 @@
+---
+---
+---
+--- 修订日期: 2024/12/20
+---
+
+local ngx = ngx
+local ngx_log = ngx.log
+local ngx_ERR = ngx.ERR
+local ngx_print = ngx.print
+local ngx_exit = ngx.exit
+local ngx_today = ngx.today
+local ngx_kv = ngx.shared
+
+local _M = {
+ version = 1.7,
+ name = "auth-plugin" -- 插件名称
+}
+
+-- 配置
+local valid_domains = {
+ "test.com", -- 需要保护的域名列表
+ "test1.cn"
+}
+
+local valid_username = "admin"
+local valid_password = "password123" -- 强密码建议修改
+local session_duration = 7200 -- 2小时,以秒为单位
+local max_login_attempts = 5 -- 最大登录失败次数
+
+-- 处理特殊字符函数,防止 HTML 注入
+local function escape_html(str)
+ if not str then return "" end
+ local replacements = {
+ ["&"] = "&",
+ ["<"] = "<",
+ [">"] = ">",
+ ['"'] = """,
+ ["'"] = "'",
+ }
+ return (str:gsub("[&<>'\"]", function(c) return replacements[c] end))
+end
+
+-- 登录页面HTML模板,带错误提示信息
+local function get_login_page(req_uri, error_message)
+ local escaped_error_message = escape_html(error_message or "")
+ local form_action = escape_html(req_uri or "/")
+
+ -- HTML部分拼接
+ return [[
+
+
+
+
+
+ 身份验证
+
+
+
+
+
身份验证
+ ]] .. (escaped_error_message ~= "" and '
' .. escaped_error_message .. '
' or "") .. [[
+
+
您的访问受保护,请输入正确的账号密码。
+
+
+
+ ]]
+end
+
+-- 校验登录请求
+local function validate_login(waf)
+ local form = waf.form["FORM"]
+ if form then
+ local username = form["username"]
+ local password = form["password"]
+ if username == valid_username and password == valid_password then
+ return true
+ end
+ end
+ return false
+end
+
+-- 请求阶段后过滤器
+function _M.req_post_filter(waf)
+ local host = waf.host
+ local req_uri = waf.reqUri
+ local method = waf.method
+
+ -- 检查域名是否受保护
+ local is_protected = false
+ for _, domain in ipairs(valid_domains) do
+ if string.lower(host) == string.lower(domain) then
+ is_protected = true
+ break
+ end
+ end
+
+ if not is_protected then
+ return
+ end
+
+ -- 检查登录失败的次数
+ local login_attempts_key = "login_attempts:" .. waf.ip .. ":" .. host
+ local login_attempts = ngx_kv.ipCache and ngx_kv.ipCache:get(login_attempts_key) or 0
+
+ if login_attempts >= max_login_attempts then
+ -- 直接拦截超出登录尝试次数的请求
+ ngx_kv.ipBlock:incr(waf.ip, 1, 0) -- 将IP拉入拦截列表
+ waf.msg = "IP因登录失败次数过多已被拦截"
+ waf.rule_id = 10001
+ waf.deny = true
+ return ngx_exit(403) -- 返回403 Forbidden
+ end
+
+ -- 校验会话是否已认证
+ local session_key = "auth:" .. waf.ip .. ":" .. host -- 结合 IP 和 域名生成唯一会话
+ local is_authenticated = ngx_kv.ipCache and ngx_kv.ipCache:get(session_key)
+
+ if not is_authenticated then
+ if method == "POST" then
+ if validate_login(waf) then
+ -- 登录成功:记录验证认证
+ ngx_kv.ipCache:set(session_key, true, session_duration)
+ ngx_kv.ipCache:delete(login_attempts_key) -- 重置失败次数
+ return
+ else
+ -- 登录失败:记录失败次数
+ login_attempts = login_attempts + 1
+ ngx_kv.ipCache:set(login_attempts_key, login_attempts, 3600) -- 失败次数保存1小时
+
+ -- 可选:输出登录失败提示(直接返回页面避免请求到原站)
+ local error_message = "用户名或密码错误,请重试。"
+ ngx.header.content_type = "text/html; charset=utf-8"
+ return ngx.print(get_login_page(req_uri, error_message)) -- 使用直接返回页面
+ end
+ else
+ -- 显示登录页面
+ ngx.header.content_type = "text/html; charset=utf-8"
+ return ngx.print(get_login_page(req_uri, nil)) -- 使用直接返回页面
+ end
+ end
+
+ -- 已经认证,继续处理后续请求
+end
+
+return _M
diff --git a/src/rules/third_party/brute-force-login-prevention.lua b/src/rules/third_party/brute-force-login-prevention.lua
new file mode 100644
index 0000000..a220532
--- /dev/null
+++ b/src/rules/third_party/brute-force-login-prevention.lua
@@ -0,0 +1,39 @@
+--[[
+规则名称: 防止爆破登录
+过滤阶段: 请求阶段
+危险等级: 高危
+规则描述: 针对路径中包含登录、注册等关键词的URL特征,如果5分钟(300秒)内请求次数超过10次,则封禁该IP 1440分钟(24小时)
+--]]
+
+local sh = waf.ipCache
+local bruteForceKey = 'brute-force-login:' .. waf.ip -- 使用独立前缀标识,避免与其他规则冲突
+
+-- 定义特征路径关键词列表
+local targetPaths = { "login", "signin", "signup", "register", "reset", "passwd", "account", "user" }
+
+-- 判断URI是否包含特征关键词
+if not waf.pmMatch(waf.toLower(waf.uri), targetPaths) then
+ return false -- 如果路径中不包含任何特征关键词,则跳过检测
+end
+
+-- 获取缓存中的数据
+local requestCount, flag = sh:get(bruteForceKey)
+if not requestCount then
+ -- 初始化计数,设置5分钟(300秒)的时间窗口
+ sh:set(bruteForceKey, 1, 300, 1)
+else
+ -- 如果标志已经为2,则IP处于封禁状态,直接拦截
+ if flag == 2 then
+ return waf.block(true) -- 阻断请求,返回403响应
+ end
+
+ -- 增加非法请求次数
+ sh:incr(bruteForceKey, 1)
+ if requestCount + 1 > 10 then
+ -- 达到爆破攻击检测阈值,标记为封禁状态,封禁时间为1440分钟(24小时)
+ sh:set(bruteForceKey, requestCount + 1, 86400, 2)
+ return true, "检测到登录接口发生爆破攻击,已封禁IP", true -- 日志载荷改为中文
+ end
+end
+
+return false
diff --git a/src/rules/third_party/frequent-block-detection.lua b/src/rules/third_party/frequent-block-detection.lua
new file mode 100644
index 0000000..7aab014
--- /dev/null
+++ b/src/rules/third_party/frequent-block-detection.lua
@@ -0,0 +1,28 @@
+--[[
+规则名称: 频繁触发攻击拦截的IP拉黑
+过滤阶段: 请求阶段
+危险等级: 高危
+规则描述: 检查当前请求的客户端IP是否在最近10分钟内频繁触发(超过30次)WAF的拦截,如果是,则拉黑IP 1440分钟,并记录日志。
+注意: 因为南墙WAF特性,此规则生效对规则ID有要求,需要将此规则与南墙自带规则的第一个规则交换位置才能生效。
+]]
+
+local sh = waf.ipCache -- 键值存储库,用于存储拉黑状态
+local ip_stats = waf.ipBlock -- 查询最近被南墙拦截的IP统计,如社区版本默认存储时间为10分钟
+local ip = waf.ip
+local block_key = "blocked-" .. ip -- 用于记录IP拉黑状态的key
+
+-- 如果IP已经被拉黑则直接拦截
+local c, f = sh:get(block_key)
+if c and f == 2 then
+ return waf.block(true) -- 重置TCP连接,不返回任何内容
+end
+
+-- 检查该IP在最近时间内是否频繁被拦截
+local recent_count = ip_stats:get(ip)
+if recent_count and recent_count > 30 then
+ -- 如果超过30次,则拉黑IP,设置1440分钟(24小时)
+ sh:set(block_key, 1, 86400, 2) -- 第三个参数86400为1440分钟(单位为秒),第四个参数2表示拉黑状态
+ return true, "IP频繁触发拦截,已被拉黑", true -- 记录日志并拦截
+end
+
+return false
diff --git a/src/rules/third_party/high-frequency-error-protection.lua b/src/rules/third_party/high-frequency-error-protection.lua
new file mode 100644
index 0000000..3cd2a37
--- /dev/null
+++ b/src/rules/third_party/high-frequency-error-protection.lua
@@ -0,0 +1,53 @@
+--[[
+规则名称: 高频错误防护
+过滤阶段: 返回HTTP头阶段
+危险等级: 中危
+规则描述: 监测频繁返回40x、50x错误,当60秒内出现这些错误10次以上,则封禁1440分钟。
+--]]
+
+local function isCommonError(status)
+ -- 检查是否为40x或50x错误
+ return status >= 400 and status < 600
+end
+
+-- 配置参数
+local threshold = 10 -- 错误次数阈值
+local timeWindow = 60 -- 时间窗口,单位为秒
+local banDuration = 1440 * 60 -- 封禁时间,1440分钟 = 86400秒
+
+-- 获取客户端IP
+local ip = waf.ip
+
+-- 获取返回的HTTP状态码
+local status = waf.status
+
+-- 检查当前请求是否是40x或者50x错误,不是则直接返回false
+if not isCommonError(status) then
+ return false
+end
+
+-- 使用 waf.ipCache 记录当前 IP 的错误次数
+local errorCache = waf.ipCache
+local errorKey = "error:" .. ip -- 定义记录错误次数的键值,以 IP 为基础区分
+
+local errorCount, flag = errorCache:get(errorKey)
+
+-- 若当前记录不存在,初始化记录
+if not errorCount then
+ errorCache:set(errorKey, 1, timeWindow) -- 初始错误计数设置为1,并设置为60秒过期
+else
+ if flag == 2 then
+ -- 标志为2表示该IP已被封禁,直接拦截,即刻终止
+ return waf.block(true)
+ end
+
+ -- 累加错误计数
+ errorCache:incr(errorKey, 1)
+ if errorCount + 1 >= threshold then
+ -- 达到错误频率阈值,标记当前IP为封禁状态
+ errorCache:set(errorKey, errorCount + 1, banDuration, 2)
+ return true, "高频错误触发,IP已被封禁", true
+ end
+end
+
+return false
diff --git a/src/rules/third_party/maintenance-mode.lua b/src/rules/third_party/maintenance-mode.lua
new file mode 100644
index 0000000..db14cea
--- /dev/null
+++ b/src/rules/third_party/maintenance-mode.lua
@@ -0,0 +1,94 @@
+--[[
+规则名称: 站点维护模式
+过滤阶段: 请求阶段
+危险等级: 低危
+规则描述: 将站点置为维护模式,返回“网页正在维护”的自定义页面。
+--]]
+
+-- 检查是否启用维护模式的条件(可以根据需求自定义,以下为示例)
+local maintenance_mode = true -- 可以通过配置文件或其他方式动态控制
+
+if maintenance_mode then
+ -- 设置自定义的维护页面 HTML 内容
+ local maintenance_html = [[
+
+
+
+
+ 网页正在维护
+
+
+
+
+
+
网页正在维护
+
抱歉,当前网页正在维护中,请稍后访问。
+
维护通知由 南墙 WAF 提供
+
+
+ ]]
+
+ ngx.header.content_type = "text/html; charset=utf-8"
+
+ -- 输出维护页面并终止请求处理
+ ngx.print(maintenance_html)
+ return ngx.exit(ngx.HTTP_OK) -- 使用 ngx.HTTP_OK 结束请求,避免传递到源站
+end
+
+return false -- 未启用维护模式,不拦截请求
From 06b4c2b699a232d354916d44b26d1b3886f58f09 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=A4=A7=E5=8A=9B=E4=B8=B8666?=
<147456216+DaLiWan666@users.noreply.github.com>
Date: Fri, 20 Dec 2024 22:31:55 +0800
Subject: [PATCH 2/3] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86=E4=BA=9B?=
=?UTF-8?q?=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../third_party/brute-force-login-prevention.lua | 2 +-
.../third_party/high-frequency-error-protection.lua | 13 +++++++------
2 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/src/rules/third_party/brute-force-login-prevention.lua b/src/rules/third_party/brute-force-login-prevention.lua
index a220532..011c5a7 100644
--- a/src/rules/third_party/brute-force-login-prevention.lua
+++ b/src/rules/third_party/brute-force-login-prevention.lua
@@ -24,7 +24,7 @@ if not requestCount then
else
-- 如果标志已经为2,则IP处于封禁状态,直接拦截
if flag == 2 then
- return waf.block(true) -- 阻断请求,返回403响应
+ return waf.block(true) -- 阻断请求
end
-- 增加非法请求次数
diff --git a/src/rules/third_party/high-frequency-error-protection.lua b/src/rules/third_party/high-frequency-error-protection.lua
index 3cd2a37..410187a 100644
--- a/src/rules/third_party/high-frequency-error-protection.lua
+++ b/src/rules/third_party/high-frequency-error-protection.lua
@@ -2,12 +2,13 @@
规则名称: 高频错误防护
过滤阶段: 返回HTTP头阶段
危险等级: 中危
-规则描述: 监测频繁返回40x、50x错误,当60秒内出现这些错误10次以上,则封禁1440分钟。
+规则描述: 监测频繁返回400、401、403、404、405、429、444错误,当60秒内出现这些错误10次以上,则封禁1440分钟。
--]]
-local function isCommonError(status)
- -- 检查是否为40x或50x错误
- return status >= 400 and status < 600
+local function isSpecifiedError(status)
+ -- 检查是否为指定的状态码,限定在 [400, 401, 403, 404, 405, 429, 444]
+ local allowed_errors = {400, 401, 403, 404, 405, 429, 444}
+ return waf.inArray(status, allowed_errors)
end
-- 配置参数
@@ -21,8 +22,8 @@ local ip = waf.ip
-- 获取返回的HTTP状态码
local status = waf.status
--- 检查当前请求是否是40x或者50x错误,不是则直接返回false
-if not isCommonError(status) then
+-- 检查当前请求是否是指定的状态码错误,不是则直接返回false
+if not isSpecifiedError(status) then
return false
end
From 202a234c11682ef4844b1c42aaf1074016260bf9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=A4=A7=E5=8A=9B=E4=B8=B8666?=
<147456216+DaLiWan666@users.noreply.github.com>
Date: Sat, 21 Dec 2024 18:01:15 +0800
Subject: [PATCH 3/3] =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E4=BA=86=E4=BA=9B?=
=?UTF-8?q?=E4=BC=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/plugins/third_party/auth-plugin.lua | 35 ++-----
.../brute-force-login-prevention.lua | 31 +++---
.../third_party/frequent-block-detection.lua | 31 +++---
.../high-frequency-error-protection.lua | 17 +---
src/rules/third_party/maintenance-mode.lua | 94 -------------------
5 files changed, 48 insertions(+), 160 deletions(-)
delete mode 100644 src/rules/third_party/maintenance-mode.lua
diff --git a/src/plugins/third_party/auth-plugin.lua b/src/plugins/third_party/auth-plugin.lua
index dd63902..3fd305c 100644
--- a/src/plugins/third_party/auth-plugin.lua
+++ b/src/plugins/third_party/auth-plugin.lua
@@ -1,7 +1,6 @@
---
----
----
---- 修订日期: 2024/12/20
+--- 作者: MCQSJ(https://github.com/MCQSJ)
+--- 更新日期: 2024/12/21
---
local ngx = ngx
@@ -13,7 +12,7 @@ local ngx_today = ngx.today
local ngx_kv = ngx.shared
local _M = {
- version = 1.7,
+ version = 0.1,
name = "auth-plugin" -- 插件名称
}
@@ -28,7 +27,6 @@ local valid_password = "password123" -- 强密码建议修改
local session_duration = 7200 -- 2小时,以秒为单位
local max_login_attempts = 5 -- 最大登录失败次数
--- 处理特殊字符函数,防止 HTML 注入
local function escape_html(str)
if not str then return "" end
local replacements = {
@@ -41,12 +39,10 @@ local function escape_html(str)
return (str:gsub("[&<>'\"]", function(c) return replacements[c] end))
end
--- 登录页面HTML模板,带错误提示信息
local function get_login_page(req_uri, error_message)
local escaped_error_message = escape_html(error_message or "")
local form_action = escape_html(req_uri or "/")
- -- HTML部分拼接
return [[
@@ -86,7 +82,6 @@ local function get_login_page(req_uri, error_message)
]]
end
--- 校验登录请求
local function validate_login(waf)
local form = waf.form["FORM"]
if form then
@@ -99,13 +94,11 @@ local function validate_login(waf)
return false
end
--- 请求阶段后过滤器
function _M.req_post_filter(waf)
local host = waf.host
local req_uri = waf.reqUri
local method = waf.method
- -- 检查域名是否受保护
local is_protected = false
for _, domain in ipairs(valid_domains) do
if string.lower(host) == string.lower(domain) then
@@ -118,48 +111,40 @@ function _M.req_post_filter(waf)
return
end
- -- 检查登录失败的次数
local login_attempts_key = "login_attempts:" .. waf.ip .. ":" .. host
local login_attempts = ngx_kv.ipCache and ngx_kv.ipCache:get(login_attempts_key) or 0
if login_attempts >= max_login_attempts then
- -- 直接拦截超出登录尝试次数的请求
- ngx_kv.ipBlock:incr(waf.ip, 1, 0) -- 将IP拉入拦截列表
+ ngx_kv.ipBlock:incr(waf.ip, 1, 0)
waf.msg = "IP因登录失败次数过多已被拦截"
waf.rule_id = 10001
waf.deny = true
- return ngx_exit(403) -- 返回403 Forbidden
+ return ngx_exit(403)
end
- -- 校验会话是否已认证
- local session_key = "auth:" .. waf.ip .. ":" .. host -- 结合 IP 和 域名生成唯一会话
+ local session_key = "auth:" .. waf.ip .. ":" .. host
local is_authenticated = ngx_kv.ipCache and ngx_kv.ipCache:get(session_key)
if not is_authenticated then
if method == "POST" then
if validate_login(waf) then
- -- 登录成功:记录验证认证
ngx_kv.ipCache:set(session_key, true, session_duration)
- ngx_kv.ipCache:delete(login_attempts_key) -- 重置失败次数
+ ngx_kv.ipCache:delete(login_attempts_key)
return
else
- -- 登录失败:记录失败次数
login_attempts = login_attempts + 1
- ngx_kv.ipCache:set(login_attempts_key, login_attempts, 3600) -- 失败次数保存1小时
+ ngx_kv.ipCache:set(login_attempts_key, login_attempts, 3600)
- -- 可选:输出登录失败提示(直接返回页面避免请求到原站)
local error_message = "用户名或密码错误,请重试。"
ngx.header.content_type = "text/html; charset=utf-8"
- return ngx.print(get_login_page(req_uri, error_message)) -- 使用直接返回页面
+ return ngx.print(get_login_page(req_uri, error_message))
end
else
- -- 显示登录页面
ngx.header.content_type = "text/html; charset=utf-8"
- return ngx.print(get_login_page(req_uri, nil)) -- 使用直接返回页面
+ return ngx.print(get_login_page(req_uri, nil))
end
end
- -- 已经认证,继续处理后续请求
end
return _M
diff --git a/src/rules/third_party/brute-force-login-prevention.lua b/src/rules/third_party/brute-force-login-prevention.lua
index 011c5a7..770f1ba 100644
--- a/src/rules/third_party/brute-force-login-prevention.lua
+++ b/src/rules/third_party/brute-force-login-prevention.lua
@@ -1,38 +1,39 @@
--[[
-规则名称: 防止爆破登录
+规则名称: 登录爆破防护
过滤阶段: 请求阶段
危险等级: 高危
-规则描述: 针对路径中包含登录、注册等关键词的URL特征,如果5分钟(300秒)内请求次数超过10次,则封禁该IP 1440分钟(24小时)
+规则描述: 针对路径中包含登录、注册等关键词的URL进行防护
+作者: MCQSJ(https://github.com/MCQSJ)
+更新日期: 2024/12/21
--]]
+-- 配置参数
+local threshold = 30 -- 错误次数阈值
+local timeWindow = 180 -- 时间窗口,单位为秒
+local banDuration = 1440 * 60 -- 封禁时间,单位为秒
+
local sh = waf.ipCache
-local bruteForceKey = 'brute-force-login:' .. waf.ip -- 使用独立前缀标识,避免与其他规则冲突
+local bruteForceKey = 'brute-force-login:' .. waf.ip
-- 定义特征路径关键词列表
local targetPaths = { "login", "signin", "signup", "register", "reset", "passwd", "account", "user" }
--- 判断URI是否包含特征关键词
if not waf.pmMatch(waf.toLower(waf.uri), targetPaths) then
- return false -- 如果路径中不包含任何特征关键词,则跳过检测
+ return false
end
--- 获取缓存中的数据
local requestCount, flag = sh:get(bruteForceKey)
if not requestCount then
- -- 初始化计数,设置5分钟(300秒)的时间窗口
- sh:set(bruteForceKey, 1, 300, 1)
+ sh:set(bruteForceKey, 1, timeWindow, 1)
else
- -- 如果标志已经为2,则IP处于封禁状态,直接拦截
if flag == 2 then
- return waf.block(true) -- 阻断请求
+ return waf.block(true)
end
- -- 增加非法请求次数
sh:incr(bruteForceKey, 1)
- if requestCount + 1 > 10 then
- -- 达到爆破攻击检测阈值,标记为封禁状态,封禁时间为1440分钟(24小时)
- sh:set(bruteForceKey, requestCount + 1, 86400, 2)
- return true, "检测到登录接口发生爆破攻击,已封禁IP", true -- 日志载荷改为中文
+ if requestCount + 1 > threshold then
+ sh:set(bruteForceKey, requestCount + 1, banDuration, 2)
+ return true, "检测到登录接口发生爆破攻击,已封禁IP", true
end
end
diff --git a/src/rules/third_party/frequent-block-detection.lua b/src/rules/third_party/frequent-block-detection.lua
index 7aab014..a3a7007 100644
--- a/src/rules/third_party/frequent-block-detection.lua
+++ b/src/rules/third_party/frequent-block-detection.lua
@@ -1,28 +1,31 @@
--[[
-规则名称: 频繁触发攻击拦截的IP拉黑
+规则名称: 高频攻击防护
过滤阶段: 请求阶段
危险等级: 高危
-规则描述: 检查当前请求的客户端IP是否在最近10分钟内频繁触发(超过30次)WAF的拦截,如果是,则拉黑IP 1440分钟,并记录日志。
-注意: 因为南墙WAF特性,此规则生效对规则ID有要求,需要将此规则与南墙自带规则的第一个规则交换位置才能生效。
+规则描述: 针对发起高频率攻击的行为进行防护
+作者: MCQSJ(https://github.com/MCQSJ)
+更新日期: 2024/12/21
+!!!注意: 因为南墙WAF特性,此规则生效对规则ID有要求,需要将此规则与南墙自带规则的第一个规则交换位置才能生效!!!
]]
-local sh = waf.ipCache -- 键值存储库,用于存储拉黑状态
-local ip_stats = waf.ipBlock -- 查询最近被南墙拦截的IP统计,如社区版本默认存储时间为10分钟
-local ip = waf.ip
-local block_key = "blocked-" .. ip -- 用于记录IP拉黑状态的key
+-- 配置参数
+local threshold = 60 -- 错误次数阈值
+local banDuration = 1440 * 60 -- 封禁时间,单位为秒
+
+local sh = waf.ipCache
+local ip_stats = waf.ipBlock
+local ip = waf.ip
+local block_key = "blocked-" .. ip
--- 如果IP已经被拉黑则直接拦截
local c, f = sh:get(block_key)
if c and f == 2 then
- return waf.block(true) -- 重置TCP连接,不返回任何内容
+ return waf.block(true)
end
--- 检查该IP在最近时间内是否频繁被拦截
local recent_count = ip_stats:get(ip)
-if recent_count and recent_count > 30 then
- -- 如果超过30次,则拉黑IP,设置1440分钟(24小时)
- sh:set(block_key, 1, 86400, 2) -- 第三个参数86400为1440分钟(单位为秒),第四个参数2表示拉黑状态
- return true, "IP频繁触发拦截,已被拉黑", true -- 记录日志并拦截
+if recent_count and recent_count > threshold then
+ sh:set(block_key, 1, banDuration, 2)
+ return true, "IP频繁触发拦截,已被拉黑", true
end
return false
diff --git a/src/rules/third_party/high-frequency-error-protection.lua b/src/rules/third_party/high-frequency-error-protection.lua
index 410187a..b0a162f 100644
--- a/src/rules/third_party/high-frequency-error-protection.lua
+++ b/src/rules/third_party/high-frequency-error-protection.lua
@@ -2,11 +2,12 @@
规则名称: 高频错误防护
过滤阶段: 返回HTTP头阶段
危险等级: 中危
-规则描述: 监测频繁返回400、401、403、404、405、429、444错误,当60秒内出现这些错误10次以上,则封禁1440分钟。
+规则描述: 针对频繁触发错误的请求的行为进行防护
+作者: MCQSJ(https://github.com/MCQSJ)
+更新日期: 2024/12/21
--]]
local function isSpecifiedError(status)
- -- 检查是否为指定的状态码,限定在 [400, 401, 403, 404, 405, 429, 444]
local allowed_errors = {400, 401, 403, 404, 405, 429, 444}
return waf.inArray(status, allowed_errors)
end
@@ -16,36 +17,28 @@ local threshold = 10 -- 错误次数阈值
local timeWindow = 60 -- 时间窗口,单位为秒
local banDuration = 1440 * 60 -- 封禁时间,1440分钟 = 86400秒
--- 获取客户端IP
local ip = waf.ip
--- 获取返回的HTTP状态码
local status = waf.status
--- 检查当前请求是否是指定的状态码错误,不是则直接返回false
if not isSpecifiedError(status) then
return false
end
--- 使用 waf.ipCache 记录当前 IP 的错误次数
local errorCache = waf.ipCache
-local errorKey = "error:" .. ip -- 定义记录错误次数的键值,以 IP 为基础区分
+local errorKey = "error:" .. ip
local errorCount, flag = errorCache:get(errorKey)
--- 若当前记录不存在,初始化记录
if not errorCount then
- errorCache:set(errorKey, 1, timeWindow) -- 初始错误计数设置为1,并设置为60秒过期
+ errorCache:set(errorKey, 1, timeWindow)
else
if flag == 2 then
- -- 标志为2表示该IP已被封禁,直接拦截,即刻终止
return waf.block(true)
end
- -- 累加错误计数
errorCache:incr(errorKey, 1)
if errorCount + 1 >= threshold then
- -- 达到错误频率阈值,标记当前IP为封禁状态
errorCache:set(errorKey, errorCount + 1, banDuration, 2)
return true, "高频错误触发,IP已被封禁", true
end
diff --git a/src/rules/third_party/maintenance-mode.lua b/src/rules/third_party/maintenance-mode.lua
deleted file mode 100644
index db14cea..0000000
--- a/src/rules/third_party/maintenance-mode.lua
+++ /dev/null
@@ -1,94 +0,0 @@
---[[
-规则名称: 站点维护模式
-过滤阶段: 请求阶段
-危险等级: 低危
-规则描述: 将站点置为维护模式,返回“网页正在维护”的自定义页面。
---]]
-
--- 检查是否启用维护模式的条件(可以根据需求自定义,以下为示例)
-local maintenance_mode = true -- 可以通过配置文件或其他方式动态控制
-
-if maintenance_mode then
- -- 设置自定义的维护页面 HTML 内容
- local maintenance_html = [[
-
-
-
-
- 网页正在维护
-
-
-
-
-
-
网页正在维护
-
抱歉,当前网页正在维护中,请稍后访问。
-
维护通知由 南墙 WAF 提供
-
-
- ]]
-
- ngx.header.content_type = "text/html; charset=utf-8"
-
- -- 输出维护页面并终止请求处理
- ngx.print(maintenance_html)
- return ngx.exit(ngx.HTTP_OK) -- 使用 ngx.HTTP_OK 结束请求,避免传递到源站
-end
-
-return false -- 未启用维护模式,不拦截请求