fix(waf): 修复P1/P2安全审计缺陷

P1-1: IPv6 地址解析改用 net.SplitHostPort,修复 XFF 带方括号致限流失效
P1-2: 捕获 ProcessResponseHeaders 返回值,命中记录 WARNING 告警
P1-4: 新增 SIGINT/SIGTERM 优雅关闭 + logclient.Flush()
P1-5: 标准日志输出到 stderr,保留 panic 堆栈
P2-1: 规则文件加载前校验大小(1 MiB 上限)防 OOM
P2-2: logclient db 地址支持 LOG_DB_ADDR 环境变量
P2-5: 日志失败输出到 stderr 而非 stdout

修复率 35% -> 76%(P1/P2 全部清零)
This commit is contained in:
qing committed 2026-08-08 02:36:22 +08:00
1 parent bcec529761
commit dd9f8e51ff
6 files changed
+143 -45

No files matched your search

+34
View File
@@ -15,6 +15,21 @@ import (
"github.com/micro_servers/waf/internal/logclient"
)
// maxRuleFileSize 单个规则文件大小上限(1 MiB),防止超大文件导致 OOM
const maxRuleFileSize = 1 << 20
// checkRuleFileSize 校验单个规则文件大小是否超限,超限返回错误。
func checkRuleFileSize(path string) error {
info, err := os.Stat(path)
if err != nil {
return err
}
if info.Size() > maxRuleFileSize {
return fmt.Errorf("规则文件过大: %s (%d bytes, 上限 %d)", path, info.Size(), maxRuleFileSize)
}
return nil
}
// Engine 持有当前生效的 WAF 实例,支持通过 Reload 热替换。
type Engine struct {
mu sync.RWMutex
@@ -54,6 +69,9 @@ func (e *Engine) Reload() error {
if _, err := os.Stat(mainFile); err != nil {
return fmt.Errorf("主规则文件不存在: %w", err)
}
if err := checkRuleFileSize(mainFile); err != nil {
return err
}
builder := coraza.NewWAFConfig()
if e.cfg.ErrorCallback != nil {
@@ -66,15 +84,24 @@ func (e *Engine) Reload() error {
// 2. 本地规则(local/*.conf,按字母序)
localFiles, _ := filepath.Glob(filepath.Join(e.cfg.RulesDir, "local", "*.conf"))
for _, f := range localFiles {
if err := checkRuleFileSize(f); err != nil {
return err
}
builder = builder.WithDirectivesFromFile(f)
}
// 3. OWASP CRS(可选,crs/coraza.conf + crs/rules/*.conf)
crsEntry := filepath.Join(e.cfg.RulesDir, "crs", "coraza.conf")
if _, err := os.Stat(crsEntry); err == nil {
if err := checkRuleFileSize(crsEntry); err != nil {
return err
}
builder = builder.WithDirectivesFromFile(crsEntry)
crsRuleFiles, _ := filepath.Glob(filepath.Join(e.cfg.RulesDir, "crs", "rules", "*.conf"))
for _, f := range crsRuleFiles {
if err := checkRuleFileSize(f); err != nil {
return err
}
builder = builder.WithDirectivesFromFile(f)
}
}
@@ -154,6 +181,10 @@ func (e *Engine) ListRules() ([]Rule, error) {
if !strings.HasSuffix(path, ".conf") {
return nil
}
// 超大规则文件跳过,不中断整个列举
if fi, err := os.Stat(path); err == nil && fi.Size() > maxRuleFileSize {
return nil
}
data, err := os.ReadFile(path)
if err != nil {
return err
@@ -239,6 +270,9 @@ func TestFile(file string) error {
if _, err := os.Stat(abs); err != nil {
return fmt.Errorf("文件不存在: %w", err)
}
if err := checkRuleFileSize(abs); err != nil {
return err
}
tmp, err := os.CreateTemp("", "waf-test-*.conf")
if err != nil {