mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-08 00:26:37 +08:00
feat(waf): 新增安全防护节点 security_check
基础特征检测九项可开关;默认开启路径穿越与文件包含;命中任意规则走 false。
This commit is contained in:
@@ -100,6 +100,9 @@ func compileRuleNodeConfig(node RuleNode) (any, error) {
|
||||
config.MatchMode = UACheckMatchModeOr
|
||||
}
|
||||
return config, nil
|
||||
case RuleNodeSecurityCheck:
|
||||
var config SecurityCheckConfig
|
||||
return config, decodeStrictConfig(node.Config, &config)
|
||||
case RuleNodeBlock:
|
||||
var config BlockNodeConfig
|
||||
return config, decodeStrictConfig(node.Config, &config)
|
||||
|
||||
@@ -42,6 +42,30 @@ func TestCompileRuleGraph(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompileSecurityCheckConfig(t *testing.T) {
|
||||
graph := RuleGraph{SchemaVersion: RuleGraphSchemaVersion, Nodes: []RuleNode{
|
||||
{ID: "start", Type: RuleNodeStart, Config: rawConfig(`{}`)},
|
||||
{ID: "sec", Type: RuleNodeSecurityCheck, Config: rawConfig(`{"path_traversal":true,"file_inclusion":true,"sql_injection":false}`)},
|
||||
{ID: "allow", Type: RuleNodeAllow, Config: rawConfig(`{}`)},
|
||||
{ID: "block", Type: RuleNodeBlock, Config: rawConfig(`{"status_code":403}`)},
|
||||
}, Edges: []RuleEdge{
|
||||
{ID: "e1", Source: "start", SourceHandle: "next", Target: "sec"},
|
||||
{ID: "e2", Source: "sec", SourceHandle: "true", Target: "allow"},
|
||||
{ID: "e3", Source: "sec", SourceHandle: "false", Target: "block"},
|
||||
}}
|
||||
compiled, err := CompileRuleGraph(graph)
|
||||
if err != nil {
|
||||
t.Fatalf("CompileRuleGraph() error = %v", err)
|
||||
}
|
||||
cfg, ok := compiled.Nodes["sec"].Config.(SecurityCheckConfig)
|
||||
if !ok {
|
||||
t.Fatalf("config type = %T", compiled.Nodes["sec"].Config)
|
||||
}
|
||||
if !cfg.PathTraversal || !cfg.FileInclusion || cfg.SQLInjection {
|
||||
t.Fatalf("unexpected config %#v", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompileUACheckConfigNormalizesListsAndMatchMode(t *testing.T) {
|
||||
graph := RuleGraph{SchemaVersion: RuleGraphSchemaVersion, Nodes: []RuleNode{
|
||||
{ID: "start", Type: RuleNodeStart, Config: rawConfig(`{}`)},
|
||||
|
||||
@@ -26,6 +26,8 @@ const (
|
||||
RuleNodePoW RuleNodeType = "pow"
|
||||
// RuleNodeUACheck branches on User-Agent presence, classification, and lists.
|
||||
RuleNodeUACheck RuleNodeType = "ua_check"
|
||||
// RuleNodeSecurityCheck branches on basic request payload attack signatures.
|
||||
RuleNodeSecurityCheck RuleNodeType = "security_check"
|
||||
)
|
||||
|
||||
// RuleGraph is the editor-facing representation of an executable WAF graph.
|
||||
@@ -103,6 +105,28 @@ const (
|
||||
UACheckMatchModeOr = "or"
|
||||
)
|
||||
|
||||
// SecurityCheckConfig toggles basic payload signature protections.
|
||||
// Default graph nodes enable path_traversal and file_inclusion only.
|
||||
type SecurityCheckConfig struct {
|
||||
SQLInjection bool `json:"sql_injection"`
|
||||
PathTraversal bool `json:"path_traversal"`
|
||||
CommandInjection bool `json:"command_injection"`
|
||||
XSS bool `json:"xss"`
|
||||
SSRF bool `json:"ssrf"`
|
||||
FileInclusion bool `json:"file_inclusion"`
|
||||
MaliciousUpload bool `json:"malicious_upload"`
|
||||
XXE bool `json:"xxe"`
|
||||
CRLFInjection bool `json:"crlf_injection"`
|
||||
}
|
||||
|
||||
// DefaultSecurityCheckConfig returns low false-positive defaults.
|
||||
func DefaultSecurityCheckConfig() SecurityCheckConfig {
|
||||
return SecurityCheckConfig{
|
||||
PathTraversal: true,
|
||||
FileInclusion: true,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultRuleGraph returns the minimal start-to-allow graph.
|
||||
func DefaultRuleGraph() RuleGraph {
|
||||
return RuleGraph{SchemaVersion: RuleGraphSchemaVersion, Nodes: []RuleNode{
|
||||
|
||||
@@ -89,7 +89,7 @@ func validateRuleGraphNodes(ctx context.Context, graphNodes []RuleNode, ipGroupE
|
||||
startID = node.ID
|
||||
case RuleNodeAllow:
|
||||
allowCount++
|
||||
case RuleNodeBlock, RuleNodeIPMatch, RuleNodeGeoMatch, RuleNodePoW, RuleNodeUACheck:
|
||||
case RuleNodeBlock, RuleNodeIPMatch, RuleNodeGeoMatch, RuleNodePoW, RuleNodeUACheck, RuleNodeSecurityCheck:
|
||||
default:
|
||||
return nil, "", fmt.Errorf("节点 %s 的类型 %s 未知", node.ID, node.Type)
|
||||
}
|
||||
@@ -184,6 +184,8 @@ func validateRuleNodeConfig(ctx context.Context, node RuleNode, exists func(cont
|
||||
return validatePoWNodeConfig(node)
|
||||
case RuleNodeUACheck:
|
||||
return validateUACheckNodeConfig(node)
|
||||
case RuleNodeSecurityCheck:
|
||||
return validateSecurityCheckNodeConfig(node)
|
||||
case RuleNodeBlock:
|
||||
return validateBlockNodeConfig(node)
|
||||
}
|
||||
@@ -329,6 +331,11 @@ func validateUACheckNodeConfig(node RuleNode) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateSecurityCheckNodeConfig(node RuleNode) error {
|
||||
var cfg SecurityCheckConfig
|
||||
return decodeNodeConfig(node, &cfg)
|
||||
}
|
||||
|
||||
var uaBrowserLabels = map[string]bool{
|
||||
"Chrome": true, "Safari": true, "Firefox": true, "Edge": true, "Opera": true,
|
||||
"Chromium": true, "WeChat": true, "Postman": true, "CLI": true, "Bot": true,
|
||||
@@ -378,7 +385,7 @@ func requiredHandles(t RuleNodeType) []string {
|
||||
switch t {
|
||||
case RuleNodeStart, RuleNodePoW:
|
||||
return []string{"next"}
|
||||
case RuleNodeIPMatch, RuleNodeGeoMatch, RuleNodeUACheck:
|
||||
case RuleNodeIPMatch, RuleNodeGeoMatch, RuleNodeUACheck, RuleNodeSecurityCheck:
|
||||
return []string{"true", "false"}
|
||||
default:
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user