mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-04 23:16:37 +08:00
feat(waf): UA 非正常不含爬虫,并支持自定义正则屏蔽
block_abnormal_ua 仅 Other/Unknown;新增 block_custom_ua 与 custom_ua_patterns。
This commit is contained in:
@@ -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 }) },
|
||||
|
||||
@@ -95,6 +95,7 @@ func compileRuleNodeConfig(node RuleNode) (any, error) {
|
||||
}
|
||||
config.Browsers = sortedUniqueStrings(config.Browsers)
|
||||
config.OperatingSystems = sortedUniqueStrings(config.OperatingSystems)
|
||||
config.CustomUAPatterns = sortedUniqueStrings(config.CustomUAPatterns)
|
||||
if config.MatchMode == "" {
|
||||
config.MatchMode = UACheckMatchModeOr
|
||||
}
|
||||
|
||||
@@ -93,6 +93,8 @@ type UACheckConfig struct {
|
||||
MatchMode string `json:"match_mode,omitempty"`
|
||||
BlockCommonBots bool `json:"block_common_bots"`
|
||||
BlockAbnormalUA bool `json:"block_abnormal_ua"`
|
||||
BlockCustomUA bool `json:"block_custom_ua"`
|
||||
CustomUAPatterns []string `json:"custom_ua_patterns,omitempty"`
|
||||
}
|
||||
|
||||
// UA check match modes.
|
||||
|
||||
@@ -16,9 +16,11 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
maxRuleGraphNodes = 128
|
||||
maxRuleGraphEdges = 256
|
||||
maxRuleGraphBytes = 256 * 1024
|
||||
maxRuleGraphNodes = 128
|
||||
maxRuleGraphEdges = 256
|
||||
maxRuleGraphBytes = 256 * 1024
|
||||
maxUACustomPatterns = 32
|
||||
maxUACustomPatternBytes = 256
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -307,6 +309,23 @@ func validateUACheckNodeConfig(node RuleNode) error {
|
||||
return fmt.Errorf("节点 %s 的操作系统标签 %s 无效", node.ID, label)
|
||||
}
|
||||
}
|
||||
if len(cfg.CustomUAPatterns) > maxUACustomPatterns {
|
||||
return fmt.Errorf("节点 %s 的自定义 UA 正则不能超过 %d 条", node.ID, maxUACustomPatterns)
|
||||
}
|
||||
for _, pattern := range cfg.CustomUAPatterns {
|
||||
if strings.TrimSpace(pattern) == "" {
|
||||
return fmt.Errorf("节点 %s 的自定义 UA 正则不能为空", node.ID)
|
||||
}
|
||||
if len(pattern) > maxUACustomPatternBytes {
|
||||
return fmt.Errorf("节点 %s 的自定义 UA 正则不能超过 %d 字节", node.ID, maxUACustomPatternBytes)
|
||||
}
|
||||
if _, err := regexp.Compile(pattern); err != nil {
|
||||
return fmt.Errorf("节点 %s 的自定义 UA 正则无效: %s", node.ID, pattern)
|
||||
}
|
||||
}
|
||||
if cfg.BlockCustomUA && len(cfg.CustomUAPatterns) == 0 {
|
||||
return fmt.Errorf("节点 %s 开启屏蔽自定义 UA 时至少需要一条正则", node.ID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -85,6 +85,14 @@ func TestValidateRuleGraph(t *testing.T) {
|
||||
g.Nodes[1].Type = RuleNodeUACheck
|
||||
g.Nodes[1].Config = rawConfig(`{"match_mode":"xor"}`)
|
||||
}, "节点 match-1 的匹配模式必须为 and 或 or"},
|
||||
{"invalid ua custom regex", func(g *RuleGraph) {
|
||||
g.Nodes[1].Type = RuleNodeUACheck
|
||||
g.Nodes[1].Config = rawConfig(`{"block_custom_ua":true,"custom_ua_patterns":["("]}`)
|
||||
}, "节点 match-1 的自定义 UA 正则无效"},
|
||||
{"custom ua requires patterns", func(g *RuleGraph) {
|
||||
g.Nodes[1].Type = RuleNodeUACheck
|
||||
g.Nodes[1].Config = rawConfig(`{"block_custom_ua":true}`)
|
||||
}, "节点 match-1 开启屏蔽自定义 UA 时至少需要一条正则"},
|
||||
{"unknown config field", func(g *RuleGraph) { g.Nodes[1].Config = rawConfig(`{"ips":[],"surprise":true}`) }, "节点 match-1 的配置无效"},
|
||||
{"null config", func(g *RuleGraph) { g.Nodes[1].Config = rawConfig(`null`) }, "节点 match-1 的配置无效"},
|
||||
{"too many nodes", func(g *RuleGraph) {
|
||||
|
||||
Reference in New Issue
Block a user