package wafenginecore import ( "SamWaf/common/domaintool" "SamWaf/common/uuid" "SamWaf/common/zlog" "SamWaf/customtype" "SamWaf/enums" "SamWaf/global" "SamWaf/innerbean" "SamWaf/model" "SamWaf/model/baseorm" "SamWaf/model/detection" "SamWaf/model/wafenginmodel" "SamWaf/utils" "SamWaf/wafenginecore/loadbalance" "SamWaf/wafenginecore/wafhttpcore" "SamWaf/wafproxy" "bytes" "context" "crypto/tls" "encoding/json" "errors" "fmt" goahocorasick "github.com/samwafgo/ahocorasick" "go.uber.org/zap" "io" "net" "net/http" _ "net/http/pprof" "net/url" "runtime/debug" "strconv" "strings" "sync" "sync/atomic" "time" ) type WafEngine struct { //主机情况(key:主机名+":"+端口,value : hostsafe信息里面有规则,ip信息等) HostTarget map[string]*wafenginmodel.HostSafe //主机和code的关系(key:主机code,value:主机名+":"+端口) HostCode map[string]string //主机域名和配置防护关系 (key:主机域名,value:主机的hostCode) HostTargetNoPort map[string]string //更多域名和配置防护关系 (key:主机域名,value:主机的hostCode) 一个主机绑定很多域名的情况 HostTargetMoreDomain map[string]string //服务在线情况(key:端口,value :服务情况) ServerOnline map[int]innerbean.ServerRunTime AllCertificate AllCertificate //所有证书 EngineCurrentStatus int // 当前waf引擎状态 //敏感词管理 Sensitive []model.Sensitive //敏感词 SensitiveManager *goahocorasick.Machine } func (waf *WafEngine) Error() string { fs := "HTTP: %d, HostCode: %d, Message: %s" return fmt.Sprintf(fs) } func (waf *WafEngine) ServeHTTP(w http.ResponseWriter, r *http.Request) { innerLogName := "WafEngine ServeHTTP" atomic.AddUint64(&global.GWAF_RUNTIME_QPS, 1) // 原子增加计数器 host := r.Host if !strings.Contains(host, ":") { // 检查请求是否使用了HTTPS if r.TLS != nil { // 请求使用了HTTPS host = host + ":443" } else { // 请求使用了HTTP host = host + ":80" } } defer func() { e := recover() if e != nil { // 捕获该协程的panic 111111 fmt.Println("11recover ", e) debug.PrintStack() // 打印堆栈信息 } }() targetCode := "" //检测是否是不检测端口的情况 if targetHost, ok := waf.HostTargetNoPort[utils.GetPureDomain(host)]; ok { host = targetHost } findHost := false target, ok := waf.HostTarget[host] if !ok { // 看看是不是泛域名情况 target, ok = waf.HostTarget[domaintool.MaskSubdomain(host)] if ok { findHost = true targetCode = target.Host.Code } } else { targetCode = target.Host.Code findHost = true } if !findHost { // 绑定更多域名的信息 targetCode, ok = waf.HostTargetMoreDomain[host] if ok { findHost = true } else { // 看看是不是泛域名情况 targetCode, ok = waf.HostTargetMoreDomain[domaintool.MaskSubdomain(host)] if ok { findHost = true } } } // 检查域名是否已经注册 if findHost == true { hostTarget := waf.HostTarget[waf.HostCode[targetCode]] hostCode := hostTarget.Host.Code incrementMonitor(hostCode) defer decrementMonitor(hostCode) //检测网站是否已关闭 if hostTarget.Host.START_STATUS == 1 { resBytes := []byte("网站已关闭

当前访问网站已关闭


") w.WriteHeader(http.StatusServiceUnavailable) if _, writeErr := w.Write(resBytes); writeErr != nil { zlog.Debug("write fail:", zap.Any("err", writeErr)) return } return } // 取出客户IP ipErr, clientIP, clientPort := waf.getClientIP(r, strings.Split(global.GCONFIG_RECORD_PROXY_HEADER, ",")...) if ipErr != nil { zlog.Error("get client error", ipErr.Error()) return } _, ok := waf.ServerOnline[hostTarget.Host.Remote_port] //检测如果访问IP和远程IP是同一个IP,且远程端口在本地Server已存在则显示配置错误 if clientIP == hostTarget.Host.Remote_ip && ok == true { resBytes := []byte("500: 配置有误" + host + " 当前IP和访问远端IP一样,且端口也一样,会造成循环问题") _, err := w.Write(resBytes) if err != nil { zlog.Debug("write fail:", zap.Any("", err)) return } return } // 获取请求报文的内容长度 contentLength := r.ContentLength var bodyByte []byte // 拷贝一份request的Body ,控制不记录大文件的情况 ,先写死的 if r.Body != nil && r.Body != http.NoBody && contentLength < (global.GCONFIG_RECORD_MAX_BODY_LENGTH) { bodyByte, _ = io.ReadAll(r.Body) // 把刚刚读出来的再写进去,不然后面解析表单数据就解析不到了 r.Body = io.NopCloser(bytes.NewBuffer(bodyByte)) } cookies, _ := json.Marshal(r.Cookies()) header := "" for key, values := range r.Header { for _, value := range values { header += key + ": " + value + "\r\n" } } region := utils.GetCountry(clientIP) currentDay, _ := strconv.Atoi(time.Now().Format("20060102")) //URL 解码 deRawQueryUrl := wafhttpcore.WafHttpCoreUrlEncode(r.URL.RawQuery, 10) datetimeNow := time.Now() weblogbean := innerbean.WebLog{ HOST: host, URL: r.RequestURI, RawQuery: deRawQueryUrl, REFERER: r.Referer(), USER_AGENT: r.UserAgent(), METHOD: r.Method, HEADER: string(header), COUNTRY: region[0], PROVINCE: region[2], CITY: region[3], SRC_IP: clientIP, SRC_PORT: clientPort, CREATE_TIME: datetimeNow.Format("2006-01-02 15:04:05"), UNIX_ADD_TIME: datetimeNow.UnixNano() / 1e6, CONTENT_LENGTH: contentLength, COOKIES: string(cookies), BODY: string(bodyByte), REQ_UUID: uuid.GenUUID(), USER_CODE: global.GWAF_USER_CODE, HOST_CODE: hostCode, TenantId: global.GWAF_TENANT_ID, RULE: "", ACTION: "通过", Day: currentDay, POST_FORM: r.PostForm.Encode(), TASK_FLAG: -1, RISK_LEVEL: 0, //危险等级 GUEST_IDENTIFICATION: "正常访客", //访客身份识别 TimeSpent: 0, NetSrcIp: utils.GetSourceClientIP(r.RemoteAddr), SrcByteBody: bodyByte, WebLogVersion: global.GWEBLOG_VERSION, Scheme: r.Proto, SrcURL: []byte(r.RequestURI), } formValues := url.Values{} if strings.Contains(r.Header.Get("Content-Type"), "application/x-www-form-urlencoded") { // 解码 x-www-form-urlencoded 数据 formValuest, err := url.ParseQuery(weblogbean.BODY) if err == nil { formValues = formValuest } else { fmt.Println("解码失败:", err) fmt.Println("解码失败:", weblogbean.BODY) } } // 检测是否已经被CC封禁 ccCacheKey := enums.CACHE_CCVISITBAN_PRE + weblogbean.NetSrcIp if global.GCACHE_WAFCACHE.IsKeyExist(ccCacheKey) { visitIPError := fmt.Sprintf("当前IP已经被CC封禁,IP:%s 归属地区:%s", weblogbean.NetSrcIp, region) global.GQEQUE_MESSAGE_DB.Enqueue(innerbean.OperatorMessageInfo{ BaseMessageInfo: innerbean.BaseMessageInfo{OperaType: "CC封禁提醒"}, OperaCnt: visitIPError, }) EchoErrorInfo(w, r, weblogbean, "", "当前IP由于访问频次太高暂时无法访问", hostTarget, waf.HostTarget[waf.HostCode[global.GWAF_GLOBAL_HOST_CODE]], false) return } if host == hostTarget.Host.Host+":80" && !strings.HasPrefix(weblogbean.URL, global.GSSL_HTTP_CHANGLE_PATH) && hostTarget.Host.AutoJumpHTTPS == 1 && hostTarget.Host.Ssl == 1 { // 重定向到 HTTPS 版本的 URL targetHttpsUrl := fmt.Sprintf("%s%s%s", "https://", r.Host, r.URL.Path) if hostTarget.Host.Port != 443 { targetHttpsUrl = fmt.Sprintf("%s%s:%d%s", "https://", r.Host, hostTarget.Host.Port, r.URL.Path) } if r.URL.RawQuery != "" { targetHttpsUrl += "?" + r.URL.RawQuery } zlog.Info(innerLogName, "jump https", targetHttpsUrl) http.Redirect(w, r, targetHttpsUrl, int(global.GCONFIG_RECORD_REDIRECT_HTTPS_CODE)) return } r.Header.Add("waf_req_uuid", weblogbean.REQ_UUID) if hostTarget.Host.GUARD_STATUS == 1 { //一系列检测逻辑 handleBlock := func(checkFunc func(*http.Request, *innerbean.WebLog, url.Values, *wafenginmodel.HostSafe, *wafenginmodel.HostSafe) detection.Result) bool { detectionResult := checkFunc(r, &weblogbean, formValues, hostTarget, waf.HostTarget[waf.HostCode[global.GWAF_GLOBAL_HOST_CODE]]) if detectionResult.IsBlock { decrementMonitor(hostCode) EchoErrorInfo(w, r, weblogbean, detectionResult.Title, detectionResult.Content, hostTarget, waf.HostTarget[waf.HostCode[global.GWAF_GLOBAL_HOST_CODE]], true) return true } return false } globalHostSafe := waf.HostTarget[waf.HostCode[global.GWAF_GLOBAL_HOST_CODE]] detectionWhiteResult := waf.CheckAllowIP(r, &weblogbean, formValues, hostTarget, globalHostSafe) if detectionWhiteResult.JumpGuardResult == false { detectionWhiteResult = waf.CheckAllowURL(r, weblogbean, formValues, hostTarget, globalHostSafe) } if detectionWhiteResult.JumpGuardResult == false { if handleBlock(waf.CheckDenyIP) { return } if handleBlock(waf.CheckDenyURL) { return } hostDefense := model.HostsDefense{ DEFENSE_BOT: 1, DEFENSE_SQLI: 1, DEFENSE_XSS: 1, DEFENSE_SCAN: 1, DEFENSE_RCE: 1, DEFENSE_SENSITIVE: 1, DEFENSE_DIR_TRAVERSAL: 1, } err := json.Unmarshal([]byte(hostTarget.Host.DEFENSE_JSON), &hostDefense) if err != nil { zlog.Debug("解析defense json失败") } //检测爬虫bot if hostDefense.DEFENSE_BOT == 1 { if handleBlock(waf.CheckBot) { return } } //检测sqli if hostDefense.DEFENSE_SQLI == 1 { if handleBlock(waf.CheckSql) { return } } //检测xss if hostDefense.DEFENSE_XSS == 1 { if handleBlock(waf.CheckXss) { return } } //检测扫描工具 if hostDefense.DEFENSE_SCAN == 1 { if handleBlock(waf.CheckSan) { return } } //检测RCE if hostDefense.DEFENSE_RCE == 1 { if handleBlock(waf.CheckRce) { return } } //目录穿越检测 if hostDefense.DEFENSE_DIR_TRAVERSAL == 1 { if handleBlock(waf.CheckDirTraversal) { return } } //检测CC if handleBlock(waf.CheckCC) { return } //规则判断 if handleBlock(waf.CheckRule) { return } //检测敏感词 if hostDefense.DEFENSE_SENSITIVE == 1 { if handleBlock(waf.CheckSensitive) { return } } //检测OWASP if handleBlock(waf.CheckOwasp) { return } // 添加防盗链检查 if handleBlock(waf.CheckAntiLeech) { return } // 验证码检测 captchaConfig := model.CaptchaConfig{ IsEnableCaptcha: 0, ExcludeURLs: "", ExpireTime: 24, IPMode: "nic", // 默认使用网卡模式 } err = json.Unmarshal([]byte(hostTarget.Host.CaptchaJSON), &captchaConfig) if err != nil { zlog.Debug("解析captcha json失败") } if captchaConfig.IsEnableCaptcha == 1 { if !waf.checkCaptchaToken(r, weblogbean, captchaConfig.IPMode) { // 检查当前URL是否在排除列表中 currentURL := strings.ToLower(r.URL.Path) isExcluded := false if len(captchaConfig.ExcludeURLs) > 0 { // 将换行分隔的URL列表拆分为数组 excludeURLs := strings.Split(captchaConfig.ExcludeURLs, "\n") for _, excludeURL := range excludeURLs { // 去除可能的空白字符并转为小写 excludeURL = strings.TrimSpace(strings.ToLower(excludeURL)) if excludeURL != "" && strings.HasPrefix(currentURL, excludeURL) { isExcluded = true break } } } if !isExcluded { waf.handleCaptchaRequest(w, r, captchaConfig.ExpireTime, weblogbean, captchaConfig.IPMode) return } } } } } //如果正常的流量不记录请求原始包 if global.GCONFIG_RECORD_ALL_SRC_BYTE_INFO == 0 { weblogbean.SrcByteBody = nil } //基本验证是否开关是否开启 if hostTarget.Host.IsEnableHttpAuthBase == 1 { bHttpAuthBaseResult, sHttpAuthBaseResult := waf.DoHttpAuthBase(hostTarget, w, r) if bHttpAuthBaseResult == true { // 记录日志 weblogbean.RES_BODY = sHttpAuthBaseResult weblogbean.ACTION = "禁止" global.GQEQUE_LOG_DB.Enqueue(weblogbean) return } } // 日志保存时候也是脱敏保存防止,数据库密码被破解,遭到敏感信息遭到泄露 if weblogbean.BODY != "" { weblogbean.BODY = utils.DeSenTextByCustomMark(enums.DLP_MARK_RULE_LoginSensitiveInfoMaskRule, weblogbean.BODY) } remoteUrl, err := url.Parse(hostTarget.TargetHost) if err != nil { zlog.Debug("target parse fail:", zap.Any("", err)) return } // 记录前置校验耗时 weblogbean.PreCheckCost = time.Now().UnixNano()/1e6 - weblogbean.UNIX_ADD_TIME // 在请求上下文中存储自定义数据 ctx := context.WithValue(r.Context(), "waf_context", innerbean.WafHttpContextData{ Weblog: &weblogbean, HostCode: hostCode, }) // 代理请求 waf.ProxyHTTP(w, r, host, remoteUrl, clientIP, ctx, weblogbean, hostTarget) return } else { resBytes := []byte("403: Host forbidden " + host) _, err := w.Write(resBytes) if err != nil { zlog.Debug("write fail:", zap.Any("", err)) return } // 获取请求报文的内容长度 contentLength := r.ContentLength //server_online[8081].Svr.Close() var bodyByte []byte // 拷贝一份request的Body ,控制不记录大文件的情况 ,先写死的 if r.Body != nil && r.Body != http.NoBody && contentLength < (global.GCONFIG_RECORD_MAX_BODY_LENGTH) { bodyByte, _ = io.ReadAll(r.Body) // 把刚刚读出来的再写进去,不然后面解析表单数据就解析不到了 r.Body = io.NopCloser(bytes.NewBuffer(bodyByte)) } cookies, _ := json.Marshal(r.Cookies()) header, _ := json.Marshal(r.Header) // 取出客户IP ipErr, clientIP, clientPort := waf.getClientIP(r, strings.Split(global.GCONFIG_RECORD_PROXY_HEADER, ",")...) if ipErr != nil { zlog.Error("get client error", ipErr.Error()) return } region := utils.GetCountry(clientIP) datetimeNow := time.Now() currentDay, _ := strconv.Atoi(datetimeNow.Format("20060102")) weblogbean := innerbean.WebLog{ HOST: r.Host, URL: r.RequestURI, REFERER: r.Referer(), USER_AGENT: r.UserAgent(), METHOD: r.Method, HEADER: string(header), COUNTRY: region[0], PROVINCE: region[2], CITY: region[3], SRC_IP: clientIP, SRC_PORT: clientPort, CREATE_TIME: datetimeNow.Format("2006-01-02 15:04:05"), UNIX_ADD_TIME: datetimeNow.UnixNano() / 1e6, CONTENT_LENGTH: contentLength, COOKIES: string(cookies), BODY: string(bodyByte), REQ_UUID: uuid.GenUUID(), USER_CODE: global.GWAF_USER_CODE, HOST_CODE: "", TenantId: global.GWAF_TENANT_ID, RULE: "", ACTION: "通过", Day: currentDay, STATUS: "禁止访问", STATUS_CODE: 403, TASK_FLAG: 1, RISK_LEVEL: 1, //危险等级 GUEST_IDENTIFICATION: "未解析域名", //访客身份识别 TimeSpent: 0, NetSrcIp: utils.GetSourceClientIP(r.RemoteAddr), WebLogVersion: global.GWEBLOG_VERSION, Scheme: r.Proto, SrcURL: []byte(r.RequestURI), } //记录响应body weblogbean.RES_BODY = string(resBytes) weblogbean.ACTION = "禁止" global.GQEQUE_LOG_DB.Enqueue(weblogbean) } } func (waf *WafEngine) getClientIP(r *http.Request, headers ...string) (error, string, string) { for _, header := range headers { ip := r.Header.Get(header) if ip != "" { // 处理可能的多个 IP 地址,以逗号分隔,取第一个有效的 ips := strings.Split(ip, ",") trimmedIP := strings.TrimSpace(ips[0]) if utils.IsValidIPv4(trimmedIP) { return nil, trimmedIP, "0" } if utils.IsValidIPv6(ip) { return nil, ip, "0" } return errors.New("invalid IPv4 address from header: " + header + " value:" + ip), "", "" } } // 如果没有找到,使用 r.RemoteAddr ip, port, err := net.SplitHostPort(r.RemoteAddr) if err != nil { return err, "", "" } // 验证 IPv4 地址 if utils.IsValidIPv4(ip) { return nil, ip, port } // 如果是 IPv6 地址,进一步检查是否是有效的 IPv6 地址 if strings.Contains(ip, ":") { // 如果是 IPv6 地址,进一步检查是否是有效的 IPv6 地址 if strings.Contains(ip, ":") { if utils.IsValidIPv6(ip) { return nil, ip, port } else { return errors.New("invalid IPv6 address from RemoteAddr: " + ip), "", "" } } } return errors.New("invalid IP address (not IPv4 or IPv6): " + ip), "", "" } func (waf *WafEngine) errorResponse() func(http.ResponseWriter, *http.Request, error) { return func(w http.ResponseWriter, req *http.Request, err error) { if wafHttpContext, ok := req.Context().Value("waf_context").(innerbean.WafHttpContextData); ok { weblogReq := wafHttpContext.Weblog requestInfo := fmt.Sprintf("Method: %s \r\nURL: %s \r\nHeaders: %v", req.Method, req.URL.String(), req.Header) zlog.Error("服务不可用 response:", zap.Any("err", err.Error()), zap.String("request_info", requestInfo)) resBytes := []byte("服务不可用

服务不可用


") //记录响应Header信息 resHeader := "" if req.Response != nil { for key, values := range req.Response.Header { for _, value := range values { resHeader += key + ": " + value + "\r\n" } } } weblogReq.ResHeader = resHeader weblogReq.ACTION = "放行" weblogReq.STATUS = "Service Unavailable" weblogReq.STATUS_CODE = 503 weblogReq.RES_BODY = fmt.Sprintf("请求相关信息:%v \r\n 错误信息:%v \r\n", requestInfo, err.Error()) weblogReq.TASK_FLAG = 1 global.GQEQUE_LOG_DB.Enqueue(weblogReq) w.WriteHeader(http.StatusServiceUnavailable) if _, writeErr := w.Write(resBytes); writeErr != nil { zlog.Debug("write fail:", zap.Any("err", writeErr)) return } } return } } func (waf *WafEngine) modifyResponse() func(*http.Response) error { return func(resp *http.Response) error { resp.Header.Set("X-Xss-Protection", "1; mode=block") if global.GCONFIG_RECORD_HIDE_SERVER_HEADER == 1 { resp.Header.Del("Server") resp.Header.Del("X-Powered-By") } r := resp.Request // 检查是否为WebSocket协议切换 if resp.StatusCode == http.StatusSwitchingProtocols { if wafHttpContext, ok := r.Context().Value("waf_context").(innerbean.WafHttpContextData); ok { weblogfrist := wafHttpContext.Weblog host := waf.HostCode[wafHttpContext.HostCode] weblogfrist.ACTION = "放行" weblogfrist.STATUS = resp.Status weblogfrist.STATUS_CODE = resp.StatusCode resHeader := "" for key, values := range resp.Header { for _, value := range values { resHeader += key + ": " + value + "\r\n" } } weblogfrist.ResHeader = resHeader datetimeNow := time.Now() weblogfrist.TimeSpent = datetimeNow.UnixNano()/1e6 - weblogfrist.UNIX_ADD_TIME weblogfrist.TASK_FLAG = 1 // 记录日志 if global.GWAF_RUNTIME_RECORD_LOG_TYPE == "all" { if waf.HostTarget[host].Host.EXCLUDE_URL_LOG == "" { global.GQEQUE_LOG_DB.Enqueue(weblogfrist) } else { lines := strings.Split(waf.HostTarget[host].Host.EXCLUDE_URL_LOG, "\n") isRecordLog := true // 检查每一行 for _, line := range lines { if strings.HasPrefix(weblogfrist.URL, line) { isRecordLog = false } } if isRecordLog { global.GQEQUE_LOG_DB.Enqueue(weblogfrist) } } } else if global.GWAF_RUNTIME_RECORD_LOG_TYPE == "abnormal" && weblogfrist.ACTION != "放行" { global.GQEQUE_LOG_DB.Enqueue(weblogfrist) } } // 对于WebSocket连接,不修改响应体,直接返回 return nil } if wafHttpContext, ok := r.Context().Value("waf_context").(innerbean.WafHttpContextData); ok { backendCheckStart := time.Now().UnixNano() / 1e6 weblogfrist := wafHttpContext.Weblog host := waf.HostCode[wafHttpContext.HostCode] weblogfrist.ACTION = "放行" weblogfrist.STATUS = resp.Status weblogfrist.STATUS_CODE = resp.StatusCode //返回内容的类型 respContentType := strings.ToLower(resp.Header.Get("Content-Type")) respContentType = strings.Replace(respContentType, "; charset=utf-8", "", -1) respContentType = strings.Replace(respContentType, "; charset=gbk", "", -1) //记录静态日志 isStaticAssist := utils.IsStaticAssist(resp, respContentType) //记录响应body if !isStaticAssist && resp.Body != nil && resp.Body != http.NoBody { ldpFlag := false // 将请求URL转为小写,用于不区分大小写的比较 lowerRequestURI := strings.ToLower(resp.Request.RequestURI) //隐私保护(局部) for i := 0; i < len(waf.HostTarget[host].LdpUrlLists); i++ { // 将规则URL也转为小写 lowerRuleURL := strings.ToLower(waf.HostTarget[host].LdpUrlLists[i].Url) if (waf.HostTarget[host].LdpUrlLists[i].CompareType == "等于" && lowerRuleURL == lowerRequestURI) || (waf.HostTarget[host].LdpUrlLists[i].CompareType == "前缀匹配" && strings.HasPrefix(lowerRequestURI, lowerRuleURL)) || (waf.HostTarget[host].LdpUrlLists[i].CompareType == "后缀匹配" && strings.HasSuffix(lowerRequestURI, lowerRuleURL)) || (waf.HostTarget[host].LdpUrlLists[i].CompareType == "包含匹配" && strings.Contains(lowerRequestURI, lowerRuleURL)) { ldpFlag = true break } } //隐私保护(全局) for i := 0; i < len(waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].LdpUrlLists); i++ { // 将全局规则URL也转为小写 lowerGlobalRuleURL := strings.ToLower(waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].LdpUrlLists[i].Url) if (waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].LdpUrlLists[i].CompareType == "等于" && lowerGlobalRuleURL == lowerRequestURI) || (waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].LdpUrlLists[i].CompareType == "前缀匹配" && strings.HasPrefix(lowerRequestURI, lowerGlobalRuleURL)) || (waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].LdpUrlLists[i].CompareType == "后缀匹配" && strings.HasSuffix(lowerRequestURI, lowerGlobalRuleURL)) || (waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].LdpUrlLists[i].CompareType == "包含匹配" && strings.Contains(lowerRequestURI, lowerGlobalRuleURL)) { ldpFlag = true break } } if ldpFlag == true { orgContentBytes, responseEncodingError := waf.getOrgContent(resp, isStaticAssist) if responseEncodingError == nil { newPayload := []byte("" + utils.DeSenText(string(orgContentBytes))) finalCompressBytes, _ := waf.compressContent(resp, isStaticAssist, newPayload) resp.Body = io.NopCloser(bytes.NewBuffer(finalCompressBytes)) // head 修改追加内容 resp.ContentLength = int64(len(finalCompressBytes)) resp.Header.Set("Content-Length", strconv.FormatInt(int64(len(finalCompressBytes)), 10)) } else { resp.Body = io.NopCloser(bytes.NewBuffer(orgContentBytes)) zlog.Warn(fmt.Sprintf("识别响应内容编码失败,隐私防护不可用 %v,请求URL: %s", responseEncodingError, r.URL.String())) } } //编码转换,自动检测网页编码 resp *http.Response orgContentBytes, responseEncodingError := waf.getOrgContent(resp, isStaticAssist) if responseEncodingError == nil { if global.GCONFIG_RECORD_RESP == 1 { if resp.ContentLength < global.GCONFIG_RECORD_MAX_RES_BODY_LENGTH { weblogfrist.RES_BODY = string(orgContentBytes) weblogfrist.SrcByteResBody = orgContentBytes } } //处理敏感词 if waf.CheckResponseSensitive() { //敏感词检测 matchBodyResult := waf.SensitiveManager.MultiPatternSearch([]rune(string(orgContentBytes)), false) if len(matchBodyResult) > 0 { sensitive := matchBodyResult[0].CustomData.(model.Sensitive) if sensitive.CheckDirection != "in" { weblogfrist.RISK_LEVEL = 1 if sensitive.Action == "deny" { EchoResponseErrorInfo(resp, *weblogfrist, "敏感词检测:"+string(matchBodyResult[0].Word), "敏感词内容", waf.HostTarget[host], waf.HostTarget[waf.HostCode[global.GWAF_GLOBAL_HOST_CODE]], true) return nil } else { words := processSensitiveWords(matchBodyResult, "in") weblogfrist.GUEST_IDENTIFICATION = "触发敏感词" weblogfrist.RULE = "敏感词检测:" + strings.Join(words, ",") for _, word := range words { orgContentBytes = bytes.ReplaceAll(orgContentBytes, []byte(word), []byte(global.GWAF_HTTP_SENSITIVE_REPLACE_STRING)) } } } } } //将数据在回写上去 finalCompressBytes, _ := waf.compressContent(resp, isStaticAssist, orgContentBytes) resp.Body = io.NopCloser(bytes.NewBuffer(finalCompressBytes)) resp.ContentLength = int64(len(finalCompressBytes)) resp.Header.Set("Content-Length", strconv.FormatInt(int64(len(finalCompressBytes)), 10)) } else { resp.Body = io.NopCloser(bytes.NewBuffer(orgContentBytes)) zlog.Warn(fmt.Sprintf("识别响应内容编码失败,响应日志,敏感词替换 不可用 %v,请求URL: %s", responseEncodingError, r.URL.String())) } } zlog.Debug("TESTChanllage", weblogfrist.HOST, weblogfrist.URL) if !isStaticAssist { datetimeNow := time.Now() weblogfrist.TimeSpent = datetimeNow.UnixNano()/1e6 - weblogfrist.UNIX_ADD_TIME if strings.HasPrefix(weblogfrist.URL, global.GSSL_HTTP_CHANGLE_PATH) && (resp.StatusCode == 404 || resp.StatusCode == 301 || resp.StatusCode == 302) { //如果远端HTTP01不存在挑战验证文件,那么我们映射到走本地再试一下 //Challenge /.well-known/acme-challenge/2NKiiETgQdPmmjlM88mH5uo6jM98PrgWwsDslaN8 urls := strings.Split(weblogfrist.URL, "/") if len(urls) == 4 { challengeFile := urls[3] //检测challengeFile是否合法 if !utils.IsValidChallengeFile(challengeFile) { return nil } //当前路径 data/vhost/domain code 变量下 // 需要读取的文件路径 filePath := utils.GetCurrentDir() + "/data/vhost/" + weblogfrist.HOST_CODE + "/.well-known/acme-challenge/" + challengeFile // 调用读取文件的函数 content, err := utils.ReadFile(filePath) if err != nil { zlog.Error("Error reading file: %v", err.Error()) } if content != "" { resp.StatusCode = http.StatusOK resp.Status = http.StatusText(http.StatusOK) resp.Body = io.NopCloser(bytes.NewBuffer([]byte(content))) resp.ContentLength = int64(len(content)) resp.Header.Set("Content-Length", strconv.FormatInt(int64(len(content)), 10)) weblogfrist.ACTION = "放行" weblogfrist.STATUS = resp.Status weblogfrist.STATUS_CODE = resp.StatusCode weblogfrist.TASK_FLAG = 1 weblogfrist.BackendCheckCost = time.Now().UnixNano()/1e6 - backendCheckStart //响应数据处理时间 global.GQEQUE_LOG_DB.Enqueue(weblogfrist) } } } else { //记录响应Header信息 resHeader := "" for key, values := range resp.Header { for _, value := range values { resHeader += key + ": " + value + "\r\n" } } weblogfrist.ResHeader = resHeader weblogfrist.ACTION = "放行" weblogfrist.STATUS = resp.Status weblogfrist.STATUS_CODE = resp.StatusCode weblogfrist.TASK_FLAG = 1 weblogfrist.BackendCheckCost = time.Now().UnixNano()/1e6 - backendCheckStart //响应数据处理时间 if global.GWAF_RUNTIME_RECORD_LOG_TYPE == "all" { if waf.HostTarget[host].Host.EXCLUDE_URL_LOG == "" { global.GQEQUE_LOG_DB.Enqueue(weblogfrist) } else { lines := strings.Split(waf.HostTarget[host].Host.EXCLUDE_URL_LOG, "\n") isRecordLog := true // 检查每一行 for _, line := range lines { if strings.HasPrefix(weblogfrist.URL, line) { isRecordLog = false } } if isRecordLog { global.GQEQUE_LOG_DB.Enqueue(weblogfrist) } } } else if global.GWAF_RUNTIME_RECORD_LOG_TYPE == "abnormal" && weblogfrist.ACTION != "放行" { global.GQEQUE_LOG_DB.Enqueue(weblogfrist) } } } } else { fmt.Println("weblog not found") } return nil } } func (waf *WafEngine) StartWaf() { waf.EngineCurrentStatus = 1 var hosts []model.Hosts //是否有初始化全局保护 global.GWAF_LOCAL_DB.Where("global_host = ?", 1).Find(&hosts) if hosts != nil && len(hosts) == 0 { //初始化全局保护 var wafGlobalHost = &model.Hosts{ BaseOrm: baseorm.BaseOrm{ USER_CODE: global.GWAF_USER_CODE, Tenant_ID: global.GWAF_TENANT_ID, CREATE_TIME: customtype.JsonTime(time.Now()), UPDATE_TIME: customtype.JsonTime(time.Now()), }, Code: uuid.GenUUID(), Host: "全局网站", Port: 0, Ssl: 0, GUARD_STATUS: 0, REMOTE_SYSTEM: "", REMOTE_APP: "", Remote_host: "", Remote_port: 0, Certfile: "", Keyfile: "", REMARKS: "", GLOBAL_HOST: 1, START_STATUS: 0, UnrestrictedPort: 0, BindSslId: "", AutoJumpHTTPS: 0, } global.GWAF_LOCAL_DB.Create(wafGlobalHost) } //初始化步骤[加载ip数据库] // 从嵌入的文件中读取内容 //global.GCACHE_IP_CBUFF = main.Ip2regionBytes //第一步 检测合法性并加入到全局 waf.LoadAllHost() wafSysLog := &model.WafSysLog{ BaseOrm: baseorm.BaseOrm{ Id: uuid.GenUUID(), USER_CODE: global.GWAF_USER_CODE, Tenant_ID: global.GWAF_TENANT_ID, CREATE_TIME: customtype.JsonTime(time.Now()), UPDATE_TIME: customtype.JsonTime(time.Now()), }, OpType: "信息", OpContent: "WAF启动", } global.GQEQUE_LOG_DB.Enqueue(wafSysLog) waf.StartAllProxyServer() } // CloseWaf 关闭waf func (waf *WafEngine) CloseWaf() { defer func() { e := recover() if e != nil { // 捕获该协程的panic 111111 zlog.Debug("关闭 recover ", e) } }() wafSysLog := &model.WafSysLog{ BaseOrm: baseorm.BaseOrm{ Id: uuid.GenUUID(), USER_CODE: global.GWAF_USER_CODE, Tenant_ID: global.GWAF_TENANT_ID, CREATE_TIME: customtype.JsonTime(time.Now()), UPDATE_TIME: customtype.JsonTime(time.Now()), }, OpType: "信息", OpContent: "WAF关闭", } global.GQEQUE_LOG_DB.Enqueue(wafSysLog) waf.EngineCurrentStatus = 0 waf.StopAllProxyServer() //重置信息 waf.HostTarget = map[string]*wafenginmodel.HostSafe{} waf.HostCode = map[string]string{} waf.HostTargetNoPort = map[string]string{} waf.ServerOnline = map[int]innerbean.ServerRunTime{} waf.AllCertificate = AllCertificate{ Mux: sync.Mutex{}, Map: map[string]*tls.Certificate{}, } } // 清除代理 func (waf *WafEngine) ClearProxy(hostCode string) { var list []model.LoadBalance global.GWAF_LOCAL_DB.Where("host_code = ? ", hostCode).Find(&list) if waf.HostTarget[waf.HostCode[hostCode]] != nil { waf.HostTarget[waf.HostCode[hostCode]].Mux.Lock() defer waf.HostTarget[waf.HostCode[hostCode]].Mux.Unlock() waf.HostTarget[waf.HostCode[hostCode]].LoadBalanceRuntime.RevProxies = []*wafproxy.ReverseProxy{} waf.HostTarget[waf.HostCode[hostCode]].LoadBalanceRuntime.WeightRoundRobinBalance = loadbalance.NewWeightRoundRobinBalance(hostCode) waf.HostTarget[waf.HostCode[hostCode]].LoadBalanceRuntime.IpHashBalance = loadbalance.NewConsistentHashBalance(nil, hostCode) waf.HostTarget[waf.HostCode[hostCode]].LoadBalanceLists = list } } // 开启所有代理 func (waf *WafEngine) StartAllProxyServer() { for _, v := range waf.ServerOnline { waf.StartProxyServer(v) } waf.EnumAllPortProxyServer() waf.ReLoadSensitive() } // 罗列端口 func (waf *WafEngine) EnumAllPortProxyServer() { onlinePorts := "" for _, v := range waf.ServerOnline { onlinePorts = strconv.Itoa(v.Port) + "," + onlinePorts } global.GWAF_RUNTIME_CURRENT_WEBPORT = onlinePorts } func (waf *WafEngine) StartProxyServer(innruntime innerbean.ServerRunTime) { if innruntime.Status == 0 { //启动完成的就不进这里了 return } if innruntime.ServerType == "" { //如果是空的就不进行了 return } go func(innruntime innerbean.ServerRunTime) { if (innruntime.ServerType) == "https" { defer func() { e := recover() if e != nil { // 捕获该协程的panic 111111 zlog.Warn("https recover ", e) } }() svr := &http.Server{ Addr: ":" + strconv.Itoa(innruntime.Port), Handler: waf, TLSConfig: &tls.Config{ GetCertificate: waf.GetCertificateFunc, }, } serclone := waf.ServerOnline[innruntime.Port] serclone.Svr = svr serclone.Status = 0 waf.ServerOnline[innruntime.Port] = serclone zlog.Info("启动HTTPS 服务器" + strconv.Itoa(innruntime.Port)) err := svr.ListenAndServeTLS("", "") if err == http.ErrServerClosed { zlog.Error("[HTTPServer] https server has been close, cause:[%v]", err) } else { //TODO 记录如果https 端口被占用的情况 记录日志 且应该推送websocket wafSysLog := model.WafSysLog{ BaseOrm: baseorm.BaseOrm{ Id: uuid.GenUUID(), USER_CODE: global.GWAF_USER_CODE, Tenant_ID: global.GWAF_TENANT_ID, CREATE_TIME: customtype.JsonTime(time.Now()), UPDATE_TIME: customtype.JsonTime(time.Now()), }, OpType: "系统运行错误", OpContent: "HTTPS端口被占用: " + strconv.Itoa(innruntime.Port) + ",请检查", } global.GQEQUE_LOG_DB.Enqueue(wafSysLog) zlog.Error("[HTTPServer] https server start fail, cause:[%v]", err) } zlog.Info("server https shutdown") } else { defer func() { e := recover() if e != nil { // 捕获该协程的panic 111111 zlog.Warn("http recover ", e) } }() svr := &http.Server{ Addr: ":" + strconv.Itoa(innruntime.Port), Handler: waf, } serclone := waf.ServerOnline[innruntime.Port] serclone.Svr = svr serclone.Status = 0 waf.ServerOnline[innruntime.Port] = serclone zlog.Info("启动HTTP 服务器" + strconv.Itoa(innruntime.Port)) err := svr.ListenAndServe() if err == http.ErrServerClosed { zlog.Warn("[HTTPServer] http server has been close, cause:[%v]", err) } else { //TODO 记录如果http 端口被占用的情况 记录日志 且应该推送websocket wafSysLog := model.WafSysLog{ BaseOrm: baseorm.BaseOrm{ Id: uuid.GenUUID(), USER_CODE: global.GWAF_USER_CODE, Tenant_ID: global.GWAF_TENANT_ID, CREATE_TIME: customtype.JsonTime(time.Now()), UPDATE_TIME: customtype.JsonTime(time.Now()), }, OpType: "系统运行错误", OpContent: "HTTP端口被占用: " + strconv.Itoa(innruntime.Port) + ",请检查", } global.GQEQUE_LOG_DB.Enqueue(wafSysLog) zlog.Error("[HTTPServer] http server start fail, cause:[%v]", err) } zlog.Info("server http shutdown") } }(innruntime) } // 关闭所有代理服务 func (waf *WafEngine) StopAllProxyServer() { for _, v := range waf.ServerOnline { waf.StopProxyServer(v) } } // 关闭指定代理服务 func (waf *WafEngine) StopProxyServer(v innerbean.ServerRunTime) { if v.Svr != nil { v.Svr.Close() } } func (waf *WafEngine) ClearCcWindows() { // 清理所有主机的IP限流器记录 for _, hostSafe := range waf.HostTarget { if hostSafe.PluginIpRateLimiter != nil { hostSafe.PluginIpRateLimiter.CleanupOldRecords() } } } // ClearCcWindowsForIP 清理特定IP的CC限流记录 func (waf *WafEngine) ClearCcWindowsForIP(ip string) { // 遍历所有主机,清理指定IP的限流记录 hostCount := 0 for hostKey, hostSafe := range waf.HostTarget { if hostSafe.PluginIpRateLimiter != nil { // 获取清理前的请求计数 var countBefore int countBefore = hostSafe.PluginIpRateLimiter.GetRequestCount(ip) // 清理该IP的限流记录 hostSafe.PluginIpRateLimiter.ClearWindowForIP(ip) hostCount++ // 获取清理后的请求计数 var countAfter int countAfter = hostSafe.PluginIpRateLimiter.GetRequestCount(ip) // 打印清理前后的计数信息 zlog.Debug(fmt.Sprintf("主机 %s 的 IP %s 限流记录: 清理前=%d, 清理后=%d", hostKey, ip, countBefore, countAfter)) } } zlog.Debug(fmt.Sprintf("已清理 IP %s 在 %d 个主机上的 CC 限流记录", ip, hostCount), zap.String("ip", ip), zap.Int("hostCount", hostCount)) }