feat(waf): UA 非正常不含爬虫,并支持自定义正则屏蔽

block_abnormal_ua 仅 Other/Unknown;新增 block_custom_ua 与 custom_ua_patterns。
This commit is contained in:
ryan
2026-07-19 11:43:45 +08:00
parent 7476c86976
commit d47ceb9971
15 changed files with 168 additions and 23 deletions
+17 -1
View File
@@ -371,6 +371,18 @@ local function parse_os_name(ua)
return match_ua_rules(string.lower(ua or ""), os_rules, "Other")
end
local function ua_matches_custom_patterns(ua, patterns)
for _, pattern in ipairs(array_or_empty(patterns)) do
if type(pattern) == "string" and pattern ~= "" then
local ok, matched = pcall(function()
return string.find(ua, pattern) ~= nil
end)
if ok and matched then return true end
end
end
return false
end
local function matches_ua_check(config)
config = config or {}
local ua = ua_trim(ngx.var.http_user_agent or "")
@@ -378,7 +390,11 @@ local function matches_ua_check(config)
local browser = parse_browser_name(ua)
local os_name = parse_os_name(ua)
if config.block_common_bots and (browser == "Bot" or os_name == "Bot") then return false end
if config.block_abnormal_ua and (browser == "Bot" or browser == "Other" or browser == "Unknown") then
-- Abnormal excludes search-engine / crawler Bot labels; use block_common_bots for those.
if config.block_abnormal_ua and (browser == "Other" or browser == "Unknown") then
return false
end
if config.block_custom_ua and ua_matches_custom_patterns(ua, config.custom_ua_patterns) then
return false
end
local browsers = array_or_empty(config.browsers)
@@ -580,11 +580,30 @@ local function test_ua_check_require_block_and_whitelist()
reset_request("ua-site", nil, nil, nil, weird_ua)
runtime.check()
assert_equal(output.exit, 403, "abnormal UA should be blocked")
reset_request("ua-site", nil, nil, nil, bot_ua)
output = {}
runtime.check()
assert_equal(output.exit, nil, "search bot should not be abnormal when bots switch is off")
reset_request("ua-site", nil, nil, nil, chrome_ua)
output = {}
runtime.check()
assert_equal(output.exit, nil, "normal browser should pass abnormal check")
runtime = load_runtime({
rule_groups = { rule(1, false, ua_graph({
block_custom_ua = true,
custom_ua_patterns = { "[Pp]ython%-requests" },
})) },
bindings = { binding("ua-site", { 1 }) },
})
reset_request("ua-site", nil, nil, nil, "python-requests/2.31.0")
runtime.check()
assert_equal(output.exit, 403, "custom regex should block matching UA")
reset_request("ua-site", nil, nil, nil, chrome_ua)
output = {}
runtime.check()
assert_equal(output.exit, nil, "custom regex should allow non-matching UA")
runtime = load_runtime({
rule_groups = { rule(1, false, ua_graph({ browsers = { "Chrome" }, match_mode = "or" })) },
bindings = { binding("ua-site", { 1 }) },