Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/Safe3/uuWAF
Browse files Browse the repository at this point in the history
  • Loading branch information
Safe3 committed Dec 30, 2024
2 parents 538900f + eb88bea commit 0d3e6af
Show file tree
Hide file tree
Showing 4 changed files with 268 additions and 0 deletions.
150 changes: 150 additions & 0 deletions src/plugins/third_party/auth-plugin.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
---
--- 作者: MCQSJ(https://github.com/MCQSJ)
--- 更新日期: 2024/12/21
---

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 = 0.1,
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 -- 最大登录失败次数

local function escape_html(str)
if not str then return "" end
local replacements = {
["&"] = "&",
["<"] = "&lt;",
[">"] = "&gt;",
['"'] = "&quot;",
["'"] = "&#39;",
}
return (str:gsub("[&<>'\"]", function(c) return replacements[c] end))
end

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 "/")

return [[
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>身份验证</title>
<style>
/* 通用样式保持简洁 */
* {margin: 0; padding: 0; box-sizing: border-box;}
body { min-height: 100vh; background: linear-gradient(120deg, #e0c3fc, #8ec5fc); display: flex; align-items: center; justify-content: center; font-family: -apple-system, BlinkMacSystemFont, sans-serif; }
.glass { background: rgba(255, 255, 255, 0.25); backdrop-filter: blur(15px); border-radius: 30px; border: 1px solid rgba(255, 255, 255, 0.3); box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15); padding: 40px; width: 90%; max-width: 480px; text-align: center; position: relative; }
h1 {color: #4a4a4a; font-size: 28px; font-weight: 600; margin-bottom: 15px;}
.error-message {color: #d9534f; font-size: 14px; margin-bottom: 20px;}
label {display: block; text-align: left; margin-bottom: 10px; font-weight: bold; color: #555;}
input[type="text"], input[type="password"] {width: 100%; padding: 12px; margin-bottom: 20px; border-radius: 15px; border: 1px solid rgba(255, 255, 255, 0.4); font-size: 14px; background: rgba(255, 255, 255, 0.2); color: #555;}
input[type="submit"] {background: linear-gradient(45deg, #6e8efb, #a777e3); color: white; padding: 12px 35px; border-radius: 25px; font-weight: 500; border: none; cursor: pointer; font-size: 16px; transition: transform 0.3s ease, box-shadow 0.3s ease;}
input[type="submit"]:hover {transform: translateY(-2px); box-shadow: 0 5px 15px rgba(110, 142, 251, 0.4);}
.note {color: #666; font-size: 12px; margin-top: 20px;}
</style>
</head>
<body>
<div class="glass">
<h1>身份验证</h1>
]] .. (escaped_error_message ~= "" and '<p class="error-message">' .. escaped_error_message .. '</p>' or "") .. [[
<form method="POST" action="]] .. form_action .. [[">
<label for="username">用户名</label>
<input type="text" id="username" name="username" placeholder="输入用户名" required>
<label for="password">密码</label>
<input type="password" id="password" name="password" placeholder="输入密码" required>
<input type="submit" value="登录">
</form>
<p class="note">您的访问受保护,请输入正确的账号密码。</p>
</div>
</body>
</html>
]]
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)
waf.msg = "IP因登录失败次数过多已被拦截"
waf.rule_id = 10001
waf.deny = true
return ngx_exit(403)
end

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)
return
else
login_attempts = login_attempts + 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))
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
40 changes: 40 additions & 0 deletions src/rules/third_party/brute-force-login-prevention.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
--[[
规则名称: 登录爆破防护
过滤阶段: 请求阶段
危险等级: 高危
规则描述: 针对路径中包含登录、注册等关键词的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 targetPaths = { "login", "signin", "signup", "register", "reset", "passwd", "account", "user" }

if not waf.pmMatch(waf.toLower(waf.uri), targetPaths) then
return false
end

local requestCount, flag = sh:get(bruteForceKey)
if not requestCount then
sh:set(bruteForceKey, 1, timeWindow, 1)
else
if flag == 2 then
return waf.block(true)
end

sh:incr(bruteForceKey, 1)
if requestCount + 1 > threshold then
sh:set(bruteForceKey, requestCount + 1, banDuration, 2)
return true, "检测到登录接口发生爆破攻击,已封禁IP", true
end
end

return false
31 changes: 31 additions & 0 deletions src/rules/third_party/frequent-block-detection.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--[[
规则名称: 高频攻击防护
过滤阶段: 请求阶段
危险等级: 高危
规则描述: 针对发起高频率攻击的行为进行防护
作者: MCQSJ(https://github.com/MCQSJ)
更新日期: 2024/12/21
!!!注意: 因为南墙WAF特性,此规则生效对规则ID有要求,需要将此规则与南墙自带规则的第一个规则交换位置才能生效!!!
]]

-- 配置参数
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

local c, f = sh:get(block_key)
if c and f == 2 then
return waf.block(true)
end

local recent_count = ip_stats:get(ip)
if recent_count and recent_count > threshold then
sh:set(block_key, 1, banDuration, 2)
return true, "IP频繁触发拦截,已被拉黑", true
end

return false
47 changes: 47 additions & 0 deletions src/rules/third_party/high-frequency-error-protection.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
--[[
规则名称: 高频错误防护
过滤阶段: 返回HTTP头阶段
危险等级: 中危
规则描述: 针对频繁触发错误的请求的行为进行防护
作者: MCQSJ(https://github.com/MCQSJ)
更新日期: 2024/12/21
--]]

local function isSpecifiedError(status)
local allowed_errors = {400, 401, 403, 404, 405, 429, 444}
return waf.inArray(status, allowed_errors)
end

-- 配置参数
local threshold = 10 -- 错误次数阈值
local timeWindow = 60 -- 时间窗口,单位为秒
local banDuration = 1440 * 60 -- 封禁时间,1440分钟 = 86400秒

local ip = waf.ip

local status = waf.status

if not isSpecifiedError(status) then
return false
end

local errorCache = waf.ipCache
local errorKey = "error:" .. ip

local errorCount, flag = errorCache:get(errorKey)

if not errorCount then
errorCache:set(errorKey, 1, timeWindow)
else
if flag == 2 then
return waf.block(true)
end

errorCache:incr(errorKey, 1)
if errorCount + 1 >= threshold then
errorCache:set(errorKey, errorCount + 1, banDuration, 2)
return true, "高频错误触发,IP已被封禁", true
end
end

return false

0 comments on commit 0d3e6af

Please sign in to comment.