1.4.5.2
This commit is contained in:
355
EdgeNode/internal/nodes/http_request_cc_plus.go
Normal file
355
EdgeNode/internal/nodes/http_request_cc_plus.go
Normal file
@@ -0,0 +1,355 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build plus
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
iplib "github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/agents"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/counters"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
maputils "github.com/TeaOSLab/EdgeNode/internal/utils/maps"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/ttlcache"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ccBlockedCounter struct {
|
||||
count int32
|
||||
updatedAt int64
|
||||
}
|
||||
|
||||
var ccBlockedMap = maputils.NewFixedMap[string, *ccBlockedCounter](65535)
|
||||
|
||||
func (this *HTTPRequest) doCC() (block bool) {
|
||||
if this.nodeConfig == nil || this.ReqServer == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var validatePath = "/GE/CC/VALIDATOR"
|
||||
var maxConnections = 30
|
||||
|
||||
// 策略
|
||||
var httpCCPolicy = this.nodeConfig.FindHTTPCCPolicyWithClusterId(this.ReqServer.ClusterId)
|
||||
var scope = firewallconfigs.FirewallScopeGlobal
|
||||
if httpCCPolicy != nil {
|
||||
if !httpCCPolicy.IsOn {
|
||||
return
|
||||
}
|
||||
if len(httpCCPolicy.RedirectsChecking.ValidatePath) > 0 {
|
||||
validatePath = httpCCPolicy.RedirectsChecking.ValidatePath
|
||||
}
|
||||
if httpCCPolicy.MaxConnectionsPerIP > 0 {
|
||||
maxConnections = httpCCPolicy.MaxConnectionsPerIP
|
||||
}
|
||||
scope = httpCCPolicy.FirewallScope()
|
||||
}
|
||||
|
||||
var ccConfig = this.web.CC
|
||||
if ccConfig == nil || !ccConfig.IsOn || (this.RawReq.URL.Path != validatePath && !ccConfig.MatchURL(this.requestScheme()+"://"+this.ReqHost+this.Path())) {
|
||||
return
|
||||
}
|
||||
|
||||
// 忽略常用文件
|
||||
if ccConfig.IgnoreCommonFiles {
|
||||
if len(this.RawReq.Referer()) > 0 {
|
||||
var ext = filepath.Ext(this.RawReq.URL.Path)
|
||||
if len(ext) > 0 && utils.IsCommonFileExtension(ext) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查白名单
|
||||
var remoteAddr = this.requestRemoteAddr(true)
|
||||
|
||||
// 检查是否为白名单直连
|
||||
if !Tea.IsTesting() && this.nodeConfig.IPIsAutoAllowed(remoteAddr) {
|
||||
return
|
||||
}
|
||||
|
||||
// 是否在全局名单中
|
||||
canGoNext, isInAllowedList, _ := iplibrary.AllowIP(remoteAddr, this.ReqServer.Id)
|
||||
if !canGoNext {
|
||||
this.disableLog = true
|
||||
this.Close()
|
||||
return true
|
||||
}
|
||||
if isInAllowedList {
|
||||
return false
|
||||
}
|
||||
|
||||
// WAF黑名单
|
||||
if waf.SharedIPBlackList.Contains(waf.IPTypeAll, scope, this.ReqServer.Id, remoteAddr) {
|
||||
this.disableLog = true
|
||||
this.Close()
|
||||
return true
|
||||
}
|
||||
|
||||
// 检查是否启用QPS
|
||||
if ccConfig.MinQPSPerIP > 0 && this.RawReq.URL.Path != validatePath {
|
||||
var avgQPS = counters.SharedCounter.IncreaseKey("QPS:"+remoteAddr, 60) / 60
|
||||
if avgQPS <= 0 {
|
||||
avgQPS = 1
|
||||
}
|
||||
if avgQPS < types.Uint32(ccConfig.MinQPSPerIP) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 检查连接数
|
||||
if conns.SharedMap.CountIPConns(remoteAddr) >= maxConnections {
|
||||
this.ccForbid(5)
|
||||
|
||||
var forbiddenTimes = this.increaseCCCounter(remoteAddr)
|
||||
waf.SharedIPBlackList.RecordIP(waf.IPTypeAll, scope, this.ReqServer.Id, remoteAddr, fasttime.Now().Unix()+int64(forbiddenTimes*1800), 0, scope == firewallconfigs.FirewallScopeGlobal, 0, 0, "CC防护拦截:并发连接数超出"+types.String(maxConnections)+"个")
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// GET302验证
|
||||
if ccConfig.EnableGET302 &&
|
||||
this.RawReq.Method == http.MethodGet &&
|
||||
!agents.SharedManager.ContainsIP(remoteAddr) /** 搜索引擎亲和性 **/ &&
|
||||
!strings.HasPrefix(this.RawReq.URL.Path, "/baidu_verify_") /** 百度验证 **/ &&
|
||||
!strings.HasPrefix(this.RawReq.URL.Path, "/google") /** Google验证 **/ {
|
||||
// 忽略搜索引擎
|
||||
var canSkip302 = false
|
||||
var ipResult = iplib.LookupIP(remoteAddr)
|
||||
if ipResult != nil && ipResult.IsOk() {
|
||||
var providerName = ipResult.ProviderName()
|
||||
canSkip302 = strings.Contains(providerName, "百度") || strings.Contains(providerName, "谷歌") || strings.Contains(providerName, "baidu") || strings.Contains(providerName, "google")
|
||||
}
|
||||
|
||||
if !canSkip302 {
|
||||
// 检查参数
|
||||
var ccWhiteListKey = "HTTP-CC-GET302-" + remoteAddr
|
||||
var currentTime = fasttime.Now().Unix()
|
||||
if this.RawReq.URL.Path == validatePath {
|
||||
this.DisableAccessLog()
|
||||
this.DisableStat()
|
||||
|
||||
// TODO 根据浏览器信息决定是否校验referer
|
||||
|
||||
if !this.checkCCRedirects(httpCCPolicy, remoteAddr) {
|
||||
return true
|
||||
}
|
||||
|
||||
var key = this.RawReq.URL.Query().Get("key")
|
||||
var pieces = strings.Split(key, ".") // key1.key2.timestamp
|
||||
if len(pieces) != 3 {
|
||||
this.ccForbid(1)
|
||||
return true
|
||||
}
|
||||
var urlKey = pieces[0]
|
||||
var timestampKey = pieces[1]
|
||||
var timestamp = pieces[2]
|
||||
|
||||
var targetURL = this.RawReq.URL.Query().Get("url")
|
||||
|
||||
var realURLKey = stringutil.Md5(sharedNodeConfig.Secret + "@" + targetURL + "@" + remoteAddr)
|
||||
if urlKey != realURLKey {
|
||||
this.ccForbid(2)
|
||||
return true
|
||||
}
|
||||
|
||||
// 校验时间
|
||||
if timestampKey != stringutil.Md5(sharedNodeConfig.Secret+"@"+timestamp) {
|
||||
this.ccForbid(3)
|
||||
return true
|
||||
}
|
||||
|
||||
var elapsedSeconds = currentTime - types.Int64(timestamp)
|
||||
if elapsedSeconds > 10 /** 10秒钟 **/ { // 如果校验时间过长,则可能阻止当前访问
|
||||
if elapsedSeconds > 300 /** 5分钟 **/ { // 如果超时时间过长就跳回原URL
|
||||
httpRedirect(this.writer, this.RawReq, targetURL, http.StatusFound)
|
||||
} else {
|
||||
this.ccForbid(4)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 加入到临时白名单
|
||||
ttlcache.SharedInt64Cache.Write(ccWhiteListKey, 1, currentTime+600 /** 10分钟 **/)
|
||||
|
||||
// 跳转回原来URL
|
||||
httpRedirect(this.writer, this.RawReq, targetURL, http.StatusFound)
|
||||
|
||||
return true
|
||||
} else {
|
||||
// 检查临时白名单
|
||||
if ttlcache.SharedInt64Cache.Read(ccWhiteListKey) == nil {
|
||||
if !this.checkCCRedirects(httpCCPolicy, remoteAddr) {
|
||||
return true
|
||||
}
|
||||
|
||||
var urlKey = stringutil.Md5(sharedNodeConfig.Secret + "@" + this.URL() + "@" + remoteAddr)
|
||||
var timestampKey = stringutil.Md5(sharedNodeConfig.Secret + "@" + types.String(currentTime))
|
||||
|
||||
// 跳转到验证URL
|
||||
this.DisableStat()
|
||||
httpRedirect(this.writer, this.RawReq, validatePath+"?key="+urlKey+"."+timestampKey+"."+types.String(currentTime)+"&url="+url.QueryEscape(this.URL()), http.StatusFound)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if this.RawReq.URL.Path == validatePath {
|
||||
// 直接跳回
|
||||
var targetURL = this.RawReq.URL.Query().Get("url")
|
||||
httpRedirect(this.writer, this.RawReq, targetURL, http.StatusFound)
|
||||
return true
|
||||
}
|
||||
|
||||
// Key
|
||||
var ccKeys = []string{}
|
||||
if ccConfig.WithRequestPath {
|
||||
ccKeys = append(ccKeys, "HTTP-CC-"+remoteAddr+"-"+this.Path()) // 这里可以忽略域名,因为一个正常用户同时访问多个域名的可能性不大
|
||||
} else {
|
||||
ccKeys = append(ccKeys, "HTTP-CC-"+remoteAddr)
|
||||
}
|
||||
|
||||
// 指纹
|
||||
if this.IsHTTPS && ccConfig.EnableFingerprint {
|
||||
var requestConn = this.RawReq.Context().Value(HTTPConnContextKey)
|
||||
if requestConn != nil {
|
||||
clientConn, ok := requestConn.(ClientConnInterface)
|
||||
if ok {
|
||||
var fingerprint = clientConn.Fingerprint()
|
||||
if len(fingerprint) > 0 {
|
||||
var fingerprintString = fmt.Sprintf("%x", fingerprint)
|
||||
if ccConfig.WithRequestPath {
|
||||
ccKeys = append(ccKeys, "HTTP-CC-"+fingerprintString+"-"+this.Path()) // 这里可以忽略域名,因为一个正常用户同时访问多个域名的可能性不大
|
||||
} else {
|
||||
ccKeys = append(ccKeys, "HTTP-CC-"+fingerprintString)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查阈值
|
||||
var thresholds = ccConfig.Thresholds
|
||||
if len(thresholds) == 0 || ccConfig.UseDefaultThresholds {
|
||||
if httpCCPolicy != nil && len(httpCCPolicy.Thresholds) > 0 {
|
||||
thresholds = httpCCPolicy.Thresholds
|
||||
} else {
|
||||
thresholds = serverconfigs.DefaultHTTPCCThresholds
|
||||
}
|
||||
}
|
||||
if len(thresholds) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var currentTime = fasttime.Now().Unix()
|
||||
|
||||
for _, threshold := range thresholds {
|
||||
if threshold.PeriodSeconds <= 0 || threshold.MaxRequests <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ccKey := range ccKeys {
|
||||
var count = counters.SharedCounter.IncreaseKey(ccKey+"-T"+types.String(threshold.PeriodSeconds), int(threshold.PeriodSeconds))
|
||||
if count >= types.Uint32(threshold.MaxRequests) {
|
||||
this.writeCode(http.StatusTooManyRequests, "Too many requests, please wait for a few minutes.", "访问过于频繁,请稍等片刻后再访问。")
|
||||
|
||||
// 记录到黑名单
|
||||
if threshold.BlockSeconds > 0 {
|
||||
// 如果被重复拦截,则加大惩罚力度
|
||||
var forbiddenTimes = this.increaseCCCounter(remoteAddr)
|
||||
waf.SharedIPBlackList.RecordIP(waf.IPTypeAll, scope, this.ReqServer.Id, remoteAddr, currentTime+int64(threshold.BlockSeconds*forbiddenTimes), 0, scope == firewallconfigs.FirewallScopeGlobal, 0, 0, "CC防护拦截:在"+types.String(threshold.PeriodSeconds)+"秒内请求超过"+types.String(threshold.MaxRequests)+"次")
|
||||
}
|
||||
|
||||
this.tags = append(this.tags, "CCProtection")
|
||||
this.isAttack = true
|
||||
|
||||
// 关闭连接
|
||||
this.writer.Flush()
|
||||
this.Close()
|
||||
|
||||
// 关闭同一个IP其他连接
|
||||
conns.SharedMap.CloseIPConns(remoteAddr)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TODO 对forbidden比较多的IP进行惩罚
|
||||
func (this *HTTPRequest) ccForbid(code int) {
|
||||
this.writeCode(http.StatusForbidden, "The request has been blocked by cc policy.", "当前请求已被CC策略拦截。")
|
||||
}
|
||||
|
||||
// 检查跳转次数
|
||||
func (this *HTTPRequest) checkCCRedirects(httpCCPolicy *nodeconfigs.HTTPCCPolicy, remoteAddr string) bool {
|
||||
// 如果无效跳转次数太多,则拦截
|
||||
var ccRedirectKey = "HTTP-CC-GET302-" + remoteAddr + "-REDIRECTS"
|
||||
|
||||
var maxRedirectDurationSeconds = 120
|
||||
var maxRedirects uint32 = 30
|
||||
var blockSeconds int64 = 3600
|
||||
|
||||
if httpCCPolicy != nil && httpCCPolicy.IsOn {
|
||||
if httpCCPolicy.RedirectsChecking.DurationSeconds > 0 {
|
||||
maxRedirectDurationSeconds = httpCCPolicy.RedirectsChecking.DurationSeconds
|
||||
}
|
||||
if httpCCPolicy.RedirectsChecking.MaxRedirects > 0 {
|
||||
maxRedirects = types.Uint32(httpCCPolicy.RedirectsChecking.MaxRedirects)
|
||||
}
|
||||
if httpCCPolicy.RedirectsChecking.BlockSeconds > 0 {
|
||||
blockSeconds = int64(httpCCPolicy.RedirectsChecking.BlockSeconds)
|
||||
}
|
||||
}
|
||||
|
||||
var countRedirects = counters.SharedCounter.IncreaseKey(ccRedirectKey, maxRedirectDurationSeconds)
|
||||
if countRedirects >= maxRedirects {
|
||||
// 加入黑名单
|
||||
var scope = firewallconfigs.FirewallScopeGlobal
|
||||
if httpCCPolicy != nil {
|
||||
scope = httpCCPolicy.FirewallScope()
|
||||
}
|
||||
|
||||
waf.SharedIPBlackList.RecordIP(waf.IPTypeAll, scope, this.ReqServer.Id, remoteAddr, fasttime.Now().Unix()+blockSeconds, 0, scope == firewallconfigs.FirewallScopeGlobal, 0, 0, "CC防护拦截:在"+types.String(maxRedirectDurationSeconds)+"秒内无效跳转"+types.String(maxRedirects)+"次")
|
||||
this.Close()
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 对CC禁用次数进行计数
|
||||
func (this *HTTPRequest) increaseCCCounter(remoteAddr string) int32 {
|
||||
counter, ok := ccBlockedMap.Get(remoteAddr)
|
||||
if !ok {
|
||||
counter = &ccBlockedCounter{
|
||||
count: 1,
|
||||
updatedAt: fasttime.Now().Unix(),
|
||||
}
|
||||
ccBlockedMap.Put(remoteAddr, counter)
|
||||
} else {
|
||||
if counter.updatedAt < fasttime.Now().Unix()-86400 /** 有效期间不要超过1天,防止无限期封禁 **/ {
|
||||
counter.count = 0
|
||||
}
|
||||
counter.updatedAt = fasttime.Now().Unix()
|
||||
if counter.count < 32 {
|
||||
counter.count++
|
||||
}
|
||||
}
|
||||
return counter.count
|
||||
}
|
||||
Reference in New Issue
Block a user