Merge pull request #518 from samwafgo/feat_anticc_condition

feat:add anticc condition
This commit is contained in:
samwafgo
2025-11-05 13:55:44 +08:00
committed by GitHub
10 changed files with 147 additions and 35 deletions

View File

@@ -11,18 +11,35 @@ import (
"SamWaf/utils"
"errors"
"fmt"
"strings"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"strings"
)
type WafAntiCCApi struct {
}
func (w *WafAntiCCApi) AddApi(c *gin.Context) {
ruleHelper := &utils.RuleHelper{}
var req request.WafAntiCCAddReq
err := c.ShouldBindJSON(&req)
if err == nil {
// 检查是否启用了前置规则
if req.IsEnableRule {
// 检查规则内容是否为空
if req.RuleContent == "" {
response.FailWithMessage("前置规则内容不能为空", c)
return
}
// 检查规则内容是否合法
err = ruleHelper.CheckRuleAvailable(req.RuleContent)
if err != nil {
response.FailWithMessage("前置规则校验失败", c)
return
}
}
err = wafAntiCCService.CheckIsExistApi(req)
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
err = wafAntiCCService.AddApi(req)
@@ -139,9 +156,25 @@ func (w *WafAntiCCApi) DelAntiCCApi(c *gin.Context) {
}
func (w *WafAntiCCApi) ModifyAntiCCApi(c *gin.Context) {
ruleHelper := &utils.RuleHelper{}
var req request.WafAntiCCEditReq
err := c.ShouldBindJSON(&req)
if err == nil {
// 检查是否启用了前置规则
if req.IsEnableRule {
// 检查规则内容是否为空
if req.RuleContent == "" {
response.FailWithMessage("前置规则内容不能为空", c)
return
}
// 检查规则内容是否合法
err = ruleHelper.CheckRuleAvailable(req.RuleContent)
if err != nil {
response.FailWithMessage("前置规则校验失败", c)
return
}
}
err = wafAntiCCService.ModifyApi(req)
if err != nil {
response.FailWithMessage("编辑发生错误", c)

24
main.go
View File

@@ -24,16 +24,10 @@ import (
"SamWaf/wafsnowflake"
"SamWaf/waftask"
"SamWaf/waftunnelengine"
"SamWaf/webplugin"
"crypto/tls"
"embed"
_ "embed"
"fmt"
dlp "github.com/bytedance/godlp"
"github.com/kardianos/service"
"go.uber.org/zap"
"golang.org/x/time/rate"
"gorm.io/gorm"
"io/ioutil"
"log"
"net"
@@ -50,6 +44,11 @@ import (
"syscall"
"time"
_ "time/tzdata"
dlp "github.com/bytedance/godlp"
"github.com/kardianos/service"
"go.uber.org/zap"
"gorm.io/gorm"
)
//go:embed exedata/ip2region.xdb
@@ -434,18 +433,7 @@ func (m *wafSystenService) run() {
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.HostTarget[globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.HostCode[msg.HostCode]].Mux.Unlock()
break
case enums.ChanTypeAnticc:
antiCC := msg.Content.(model.AntiCC)
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.HostTarget[globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.HostCode[msg.HostCode]].Mux.Lock()
if antiCC.Id == "" {
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.HostTarget[globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.HostCode[msg.HostCode]].PluginIpRateLimiter = nil
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.HostTarget[globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.HostCode[msg.HostCode]].AntiCCBean = antiCC
} else {
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.HostTarget[globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.HostCode[msg.HostCode]].PluginIpRateLimiter = webplugin.NewIPRateLimiter(rate.Limit(msg.Content.(model.AntiCC).Rate), msg.Content.(model.AntiCC).Limit)
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.HostTarget[globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.HostCode[msg.HostCode]].AntiCCBean = antiCC
}
zlog.Debug("远程配置", zap.Any("Anticc", msg.Content.(model.AntiCC)))
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.HostTarget[globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.HostCode[msg.HostCode]].Mux.Unlock()
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.ApplyAntiCCConfig(msg.HostCode, msg.Content.(model.AntiCC))
break
case enums.ChanTypeHttpauth:
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.HostTarget[globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.HostCode[msg.HostCode]].Mux.Lock()

View File

@@ -16,6 +16,8 @@ type AntiCC struct {
LockIPMinutes int `json:"lock_ip_minutes" gorm:"column:lock_ip_minutes"`
LimitMode string `json:"limit_mode" gorm:"column:limit_mode"` // "rate" 或 "window"
IPMode string `json:"ip_mode" gorm:"column:ip_mode"` // "nic" 网卡模式 或 "proxy" 代理模式
Url string `json:"url"` //保护的url
Remarks string `json:"remarks"` //备注
Url string `json:"url"`
IsEnableRule bool `json:"is_enable_rule" gorm:"column:is_enable_rule"` //是否启动规则
RuleContent string `json:"rule_content" gorm:"column:rule_content"` //规则内容
Remarks string `json:"remarks"` //备注
}

View File

@@ -10,6 +10,8 @@ type WafAntiCCAddReq struct {
LockIPMinutes int `json:"lock_ip_minutes" form:"lock_ip_minutes"` //封禁分钟
LimitMode string `json:"limit_mode" form:"limit_mode"` // "rate" 或 "window"
IPMode string `json:"ip_mode" form:"ip_mode"` // "nic" 网卡模式 或 "proxy" 代理模式
IsEnableRule bool `json:"is_enable_rule" form:"is_enable_rule"` //是否启动规则
RuleContent string `json:"rule_content" form:"rule_content"` //规则内容
Remarks string `json:"remarks" form:"remarks"` //备注
}
type WafAntiCCSearchReq struct {
@@ -31,6 +33,8 @@ type WafAntiCCEditReq struct {
LockIPMinutes int `json:"lock_ip_minutes" form:"lock_ip_minutes"` //封禁分钟
LimitMode string `json:"limit_mode" form:"limit_mode"` // "rate" 或 "window"
IPMode string `json:"ip_mode" form:"ip_mode"` // "nic" 网卡模式 或 "proxy" 代理模式
IsEnableRule bool `json:"is_enable_rule" form:"is_enable_rule"` //是否启动规则
RuleContent string `json:"rule_content" form:"rule_content"` //规则内容
Remarks string `json:"remarks" form:"remarks"` //备注
}

View File

@@ -38,6 +38,8 @@ func (receiver *WafAntiCCService) AddApi(req request.WafAntiCCAddReq) error {
Remarks: req.Remarks,
LimitMode: req.LimitMode,
IPMode: req.IPMode,
IsEnableRule: req.IsEnableRule,
RuleContent: req.RuleContent,
}
global.GWAF_LOCAL_DB.Create(bean)
return nil
@@ -64,6 +66,8 @@ func (receiver *WafAntiCCService) ModifyApi(req request.WafAntiCCEditReq) error
"UPDATE_TIME": customtype.JsonTime(time.Now()),
"LimitMode": req.LimitMode,
"IPMode": req.IPMode,
"IsEnableRule": req.IsEnableRule,
"RuleContent": req.RuleContent,
}
err := global.GWAF_LOCAL_DB.Model(model.AntiCC{}).Where("id = ?", req.Id).Updates(ipWhiteMap).Error

View File

@@ -26,6 +26,16 @@ func (rulehelper *RuleHelper) InitRuleEngine() {
rulehelper.ruleBuilder = builder.NewRuleBuilder(rulehelper.knowledgeLibrary)
rulehelper.engine = engine.NewGruleEngine()
}
func (rulehelper *RuleHelper) LoadRuleString(ruleContent string) error {
byteArr := pkg.NewBytesResource([]byte(ruleContent))
err := rulehelper.ruleBuilder.BuildRuleFromResource("Region", "0.0.1", byteArr)
if err != nil {
zlog.Error("LoadRule", err)
}
rulehelper.KnowledgeBase, err = rulehelper.knowledgeLibrary.NewKnowledgeBaseInstance("Region", "0.0.1")
return err
}
func (rulehelper *RuleHelper) LoadRule(ruleconfig model.Rules) error {
byteArr := pkg.NewBytesResource([]byte(ruleconfig.RuleContent))

View File

@@ -1,6 +1,7 @@
package wafenginecore
import (
"SamWaf/common/zlog"
"SamWaf/enums"
"SamWaf/global"
"SamWaf/innerbean"
@@ -32,15 +33,34 @@ func (waf *WafEngine) CheckCC(r *http.Request, weblogbean *innerbean.WebLog, for
// 默认使用网卡模式
clientIP = weblogbean.NetSrcIp
}
if !hostTarget.PluginIpRateLimiter.Allow(clientIP) {
weblogbean.RISK_LEVEL = 1
result.IsBlock = true
result.Title = "【局部】触发IP频次访问限制"
result.Content = "您的访问被阻止超量了"
cacheKey := enums.CACHE_CCVISITBAN_PRE + clientIP
//将该IP添加到封禁里
global.GCACHE_WAFCACHE.SetWithTTl(cacheKey, 1, time.Duration(hostTarget.AntiCCBean.LockIPMinutes)*time.Minute)
isCheckCC := false
if hostTarget.AntiCCBean.IsEnableRule {
if hostTarget.PluginIpRateLimiter.Rule != nil {
if hostTarget.PluginIpRateLimiter.Rule.KnowledgeBase != nil {
ruleMatchs, err := hostTarget.PluginIpRateLimiter.Rule.Match("MF", weblogbean)
if err == nil {
if len(ruleMatchs) > 0 {
isCheckCC = true
zlog.Debug("CheckCC ruleMatchs: %v", ruleMatchs)
}
}
}
}
} else {
isCheckCC = true
}
if isCheckCC {
if !hostTarget.PluginIpRateLimiter.Allow(clientIP) {
weblogbean.RISK_LEVEL = 1
result.IsBlock = true
result.Title = "【局部】触发IP频次访问限制"
result.Content = "您的访问被阻止超量了"
cacheKey := enums.CACHE_CCVISITBAN_PRE + clientIP
//将该IP添加到封禁里
global.GCACHE_WAFCACHE.SetWithTTl(cacheKey, 1, time.Duration(hostTarget.AntiCCBean.LockIPMinutes)*time.Minute)
return result
}
} else {
return result
}
}

View File

@@ -18,15 +18,13 @@ import (
"SamWaf/wafenginecore/wafhttpserver"
"SamWaf/wafenginecore/wafwebcache"
"SamWaf/wafproxy"
"SamWaf/webplugin"
"bytes"
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"github.com/pires/go-proxyproto"
goahocorasick "github.com/samwafgo/ahocorasick"
"go.uber.org/zap"
"io"
"io/ioutil"
"net"
@@ -38,6 +36,11 @@ import (
"strings"
"sync"
"time"
"github.com/pires/go-proxyproto"
goahocorasick "github.com/samwafgo/ahocorasick"
"go.uber.org/zap"
"golang.org/x/time/rate"
)
type WafEngine struct {
@@ -1431,3 +1434,42 @@ func (waf *WafEngine) ClearCcWindowsForIP(ip string) {
zap.String("ip", ip),
zap.Int("hostCount", hostCount))
}
func (waf *WafEngine) ApplyAntiCCConfig(hostCode string, antiCC model.AntiCC) {
targetKey, ok := waf.HostCode[hostCode]
if !ok {
zlog.Debug("Anticc reload skip: hostCode not found", zap.String("hostCode", hostCode))
return
}
hostSafe, ok := waf.HostTarget[targetKey]
if !ok {
zlog.Debug("Anticc reload skip: hostTarget not found", zap.String("targetKey", targetKey))
return
}
hostSafe.Mux.Lock()
defer hostSafe.Mux.Unlock()
if antiCC.Id == "" {
// 关闭CC防护
hostSafe.PluginIpRateLimiter = nil
hostSafe.AntiCCBean = antiCC
zlog.Debug("Anticc disabled", zap.String("hostCode", hostCode))
return
}
// 与初始化逻辑保持一致:支持滑动窗口/平均速率
if antiCC.LimitMode == "window" {
hostSafe.PluginIpRateLimiter = webplugin.NewWindowIPRateLimiter(antiCC.Rate, antiCC.Limit)
} else {
hostSafe.PluginIpRateLimiter = webplugin.NewIPRateLimiter(rate.Limit(antiCC.Rate), antiCC.Limit)
}
if antiCC.IsEnableRule {
hostSafe.PluginIpRateLimiter.Rule = &utils.RuleHelper{}
hostSafe.PluginIpRateLimiter.Rule.InitRuleEngine()
hostSafe.PluginIpRateLimiter.Rule.LoadRuleString(antiCC.RuleContent)
}
hostSafe.AntiCCBean = antiCC
zlog.Debug("远程配置", zap.Any("Anticc", antiCC))
// ... existing code ...
}

View File

@@ -14,12 +14,13 @@ import (
"context"
"encoding/base64"
"fmt"
goahocorasick "github.com/samwafgo/ahocorasick"
"golang.org/x/time/rate"
"net/http"
"strconv"
"strings"
"time"
goahocorasick "github.com/samwafgo/ahocorasick"
"golang.org/x/time/rate"
)
// 加载全部host
@@ -162,6 +163,12 @@ func (waf *WafEngine) LoadHost(inHost model.Hosts) []innerbean.ServerRunTime {
zlog.Debug(fmt.Sprintf("初始化CC防护(平均速率模式) 主机%v 时间窗口(秒)%v 最大请求数%v 每秒速率%v",
inHost.Host, anticcBean.Rate, anticcBean.Limit, float64(anticcBean.Limit)/float64(anticcBean.Rate)))
}
if anticcBean.IsEnableRule {
pluginIpRateLimiter.Rule = &utils.RuleHelper{}
pluginIpRateLimiter.Rule.InitRuleEngine()
pluginIpRateLimiter.Rule.LoadRuleString(anticcBean.RuleContent)
}
}
//查询ip白名单

View File

@@ -1,6 +1,7 @@
package webplugin
import (
"SamWaf/utils"
"sync"
"time"
@@ -26,6 +27,7 @@ type IPRateLimiter struct {
mode LimitMode
window int // 时间窗口大小(秒)
requests map[string][]time.Time // 用于滑动窗口模式记录请求时间
Rule *utils.RuleHelper
}
// NewIPRateLimiter 创建一个新的IP限流器