diff --git a/internal/waf/action_js_cookie.go b/internal/waf/action_js_cookie.go new file mode 100644 index 0000000..3c62f07 --- /dev/null +++ b/internal/waf/action_js_cookie.go @@ -0,0 +1,128 @@ +// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package waf + +import ( + "crypto/md5" + "fmt" + "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" + "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" + "github.com/TeaOSLab/EdgeNode/internal/ttlcache" + "github.com/TeaOSLab/EdgeNode/internal/waf/requests" + "github.com/iwind/TeaGo/types" + "net/http" + "time" +) + +type JSCookieAction struct { + BaseAction + + Life int32 `yaml:"life" json:"life"` + MaxFails int `yaml:"maxFails" json:"maxFails"` // 最大失败次数 + FailBlockTimeout int `yaml:"failBlockTimeout" json:"failBlockTimeout"` // 失败拦截时间 + Scope string `yaml:"scope" json:"scope"` +} + +func (this *JSCookieAction) Init(waf *WAF) error { + this.Scope = firewallconfigs.FirewallScopeGlobal + + return nil +} + +func (this *JSCookieAction) Code() string { + return ActionJavascriptCookie +} + +func (this *JSCookieAction) IsAttack() bool { + return false +} + +func (this *JSCookieAction) WillChange() bool { + return true +} + +func (this *JSCookieAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, req requests.Request, writer http.ResponseWriter) (allow bool) { + // 是否在白名单中 + if SharedIPWhiteList.Contains("set:"+types.String(set.Id), this.Scope, req.WAFServerId(), req.WAFRemoteIP()) { + return true + } + + nodeConfig, err := nodeconfigs.SharedNodeConfig() + if err != nil { + return true + } + + var life = this.Life + if life <= 0 { + life = 3600 + } + + // 检查Cookie + var cookieName = "ge_js_validator_" + types.String(set.Id) + cookie, err := req.WAFRaw().Cookie(cookieName) + if err == nil && cookie != nil { + var cookieValue = cookie.Value + if len(cookieValue) > 10 { + var timestamp = cookieValue[:10] + if types.Int64(timestamp) >= time.Now().Unix()-int64(life) && fmt.Sprintf("%x", md5.Sum([]byte(timestamp+"@"+nodeConfig.NodeId))) == cookieValue[10:] { + return true + } + } + } + + writer.Header().Set("Content-Type", "text/html; charset=utf-8") + writer.Header().Set("Cache-Control", "no-cache") + + var timestamp = types.String(time.Now().Unix()) + + var cookieValue = timestamp + fmt.Sprintf("%x", md5.Sum([]byte(timestamp+"@"+nodeConfig.NodeId))) + + _, _ = writer.Write([]byte(` + + + + + + + + +`)) + + // 记录失败次数 + this.increaseFails(req, waf.Id, group.Id, set.Id) + + return false +} + +func (this *JSCookieAction) increaseFails(req requests.Request, policyId int64, groupId int64, setId int64) (goNext bool) { + var maxFails = this.MaxFails + var failBlockTimeout = this.FailBlockTimeout + + if maxFails <= 0 { + maxFails = 10 // 默认10次 + } else if maxFails <= 3 { + maxFails = 3 // 不能小于3,防止意外刷新出现 + } + if failBlockTimeout <= 0 { + failBlockTimeout = 1800 // 默认1800s + } + + var key = "JS_COOKIE:FAILS:" + req.WAFRemoteIP() + ":" + types.String(req.WAFServerId()) + + var countFails = ttlcache.SharedCache.IncreaseInt64(key, 1, time.Now().Unix()+300, true) + if int(countFails) >= maxFails { + var useLocalFirewall = false + + if this.Scope == firewallconfigs.FirewallScopeGlobal { + useLocalFirewall = true + } + + SharedIPBlackList.RecordIP(IPTypeAll, firewallconfigs.FirewallScopeService, req.WAFServerId(), req.WAFRemoteIP(), time.Now().Unix()+int64(failBlockTimeout), policyId, useLocalFirewall, groupId, setId, "JS_COOKIE验证连续失败超过"+types.String(maxFails)+"次") + return false + } + + return true +} diff --git a/internal/waf/action_types.go b/internal/waf/action_types.go index 75aeace..2ce8778 100644 --- a/internal/waf/action_types.go +++ b/internal/waf/action_types.go @@ -5,18 +5,19 @@ import "reflect" type ActionString = string const ( - ActionLog ActionString = "log" // allow and log - ActionBlock ActionString = "block" // block - ActionCaptcha ActionString = "captcha" // block and show captcha - ActionNotify ActionString = "notify" // 告警 - ActionGet302 ActionString = "get_302" // 针对GET的302重定向认证 - ActionPost307 ActionString = "post_307" // 针对POST的307重定向认证 - ActionRecordIP ActionString = "record_ip" // 记录IP - ActionTag ActionString = "tag" // 标签 - ActionPage ActionString = "page" // 显示网页 - ActionAllow ActionString = "allow" // allow - ActionGoGroup ActionString = "go_group" // go to next rule group - ActionGoSet ActionString = "go_set" // go to next rule set + ActionLog ActionString = "log" // allow and log + ActionBlock ActionString = "block" // block + ActionCaptcha ActionString = "captcha" // block and show captcha + ActionJavascriptCookie ActionString = "js_cookie" // js cookie + ActionNotify ActionString = "notify" // 告警 + ActionGet302 ActionString = "get_302" // 针对GET的302重定向认证 + ActionPost307 ActionString = "post_307" // 针对POST的307重定向认证 + ActionRecordIP ActionString = "record_ip" // 记录IP + ActionTag ActionString = "tag" // 标签 + ActionPage ActionString = "page" // 显示网页 + ActionAllow ActionString = "allow" // allow + ActionGoGroup ActionString = "go_group" // go to next rule group + ActionGoSet ActionString = "go_set" // go to next rule set ) var AllActions = []*ActionDefinition{ @@ -44,6 +45,12 @@ var AllActions = []*ActionDefinition{ Instance: new(CaptchaAction), Type: reflect.TypeOf(new(CaptchaAction)).Elem(), }, + { + Name: "JS Cookie验证", + Code: ActionJavascriptCookie, + Instance: new(JSCookieAction), + Type: reflect.TypeOf(new(JSCookieAction)).Elem(), + }, { Name: "告警", Code: ActionNotify, diff --git a/internal/waf/captcha_counter.go b/internal/waf/captcha_counter.go index 7007bc3..8a974d1 100644 --- a/internal/waf/captcha_counter.go +++ b/internal/waf/captcha_counter.go @@ -34,7 +34,7 @@ func CaptchaIncreaseFails(req requests.Request, actionConfig *CaptchaAction, pol useLocalFirewall = true } - SharedIPBlackList.RecordIP(IPTypeAll, firewallconfigs.FirewallScopeService, req.WAFServerId(), req.WAFRemoteIP(), time.Now().Unix()+int64(failBlockTimeout), policyId, useLocalFirewall, groupId, setId, "CAPTCHA验证连续失败") + SharedIPBlackList.RecordIP(IPTypeAll, firewallconfigs.FirewallScopeService, req.WAFServerId(), req.WAFRemoteIP(), time.Now().Unix()+int64(failBlockTimeout), policyId, useLocalFirewall, groupId, setId, "CAPTCHA验证连续失败超过"+types.String(maxFails)+"次") return false } }