From b905707cbb95141b8666f6d38e7e473057b2acc6 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 22 Nov 2024 15:45:10 +0800 Subject: [PATCH] fix: enhance debug logging for bcontains expression evaluation Add detailed debug logging to troubleshoot string matching issues in bcontains: - Log original search string before escape processing - Log processed search string after escape handling - Log actual response body content for comparison This helps diagnose why some valid expressions are not matching as expected. --- config/config.yaml | 4 +- pkg/stage/assets/fingerprints.json | 10 +- pkg/stage/poc.go | 211 +++++++++++++---------------- pkg/stage/service.go | 14 +- 4 files changed, 112 insertions(+), 127 deletions(-) diff --git a/config/config.yaml b/config/config.yaml index 3558b0f..aee00eb 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -70,6 +70,7 @@ tcp_ports: - 7080 # Generic - 7080 # Generic - 7680 # Windows Update Delivery Optimization + - 7788 - 8000 - 8001 # HTTP Alternate - 8002 # HTTP Alternate @@ -80,9 +81,8 @@ tcp_ports: - 8081 # HTTP Alternate - 8082 # HTTP Alternate - 8083 # HTTP Alternate - - 8008 - - 8012 - 8084 # HTTP Alternate + - 8089 - 8090 # HTTP Alternate - 8111 - 8123 diff --git a/pkg/stage/assets/fingerprints.json b/pkg/stage/assets/fingerprints.json index 758ff36..e5eb398 100644 --- a/pkg/stage/assets/fingerprints.json +++ b/pkg/stage/assets/fingerprints.json @@ -1369,8 +1369,14 @@ "body":[ "视频编码设备接入网关" ] - } + }, + "ifw8-router":{ + "type": "ipcamera", + "manufacturer": "ifw8", + "body": [ + "(?i)www.ifw8.cn" + ] +} } - \ No newline at end of file diff --git a/pkg/stage/poc.go b/pkg/stage/poc.go index e205107..843afc0 100644 --- a/pkg/stage/poc.go +++ b/pkg/stage/poc.go @@ -34,9 +34,9 @@ type Rule struct { } type POCResult struct { - CVEID string `json:"cve-id"` - Severity string `json:"severity"` - Type string `json:"type"` + CVEID string `json:"cve-id,omitempty"` + Severity string `json:"severity,omitempty"` + Type string `json:"type,omitempty"` } type POCContext struct { @@ -63,8 +63,8 @@ func NewPOCExecutor(client *http.Client) *POCExecutor { } } -func (pe *POCExecutor) ExecutePOC(poc *POC, target string) POCResult { - result := POCResult{ +func (pe *POCExecutor) ExecutePOC(poc *POC, target string) *POCResult { + result := &POCResult{ Severity: poc.Severity, } @@ -84,9 +84,12 @@ func (pe *POCExecutor) ExecutePOC(poc *POC, target string) POCResult { path := replaceVariables(rule.Path, ctx) url := fmt.Sprintf("%s%s", target, path) + fmt.Printf("[DEBUG] Trying URL: %s\n", url) + body := replaceVariables(rule.Body, ctx) req, err := http.NewRequest(rule.Method, url, strings.NewReader(body)) if err != nil { + fmt.Printf("[ERROR] Failed to create request: %v\n", err) continue } @@ -107,15 +110,21 @@ func (pe *POCExecutor) ExecutePOC(poc *POC, target string) POCResult { resp, err := pe.client.Do(req) if err != nil { + fmt.Printf("[ERROR] Request failed: %v\n", err) continue } defer resp.Body.Close() + fmt.Printf("[DEBUG] Response Status: %d\n", resp.StatusCode) + respBody, err := io.ReadAll(resp.Body) if err != nil { + fmt.Printf("[ERROR] Failed to read response body: %v\n", err) continue } + fmt.Printf("[DEBUG] Response Body (first 200 chars): %s\n", string(respBody)[:min(200, len(string(respBody)))]) + // 处理 search 匹配 if rule.Search != "" { re, err := pe.getRegexp(rule.Search) @@ -151,12 +160,15 @@ func (pe *POCExecutor) ExecutePOC(poc *POC, target string) POCResult { // 处理 expression 匹配 if rule.Expression != "" { + fmt.Printf("[DEBUG] Evaluating expression: %s\n", rule.Expression) isVulnerable := evaluateExpression(rule.Expression, &ExprContext{ StatusCode: resp.StatusCode, Body: string(respBody), Headers: resp.Header, }) + fmt.Printf("[DEBUG] Expression result: %v\n", isVulnerable) + if isVulnerable { result.CVEID = poc.CVEID result.Type = poc.Type @@ -167,7 +179,8 @@ func (pe *POCExecutor) ExecutePOC(poc *POC, target string) POCResult { } } - return result + // 没有发现漏洞时返回 nil + return nil } func (pe *POCExecutor) getRegexp(pattern string) (*regexp.Regexp, error) { @@ -347,67 +360,78 @@ func evaluateSetExpression(expr string) string { } func evaluateExpression(expr string, ctx *ExprContext) bool { - // Support AND operation + fmt.Printf("\n[DEBUG] ====== Starting Expression Evaluation ======\n") + fmt.Printf("[DEBUG] Expression: %q\n", expr) + fmt.Printf("[DEBUG] Context - StatusCode: %d, Body length: %d\n", ctx.StatusCode, len(ctx.Body)) + + // 处理 AND 操作 if strings.Contains(expr, "&&") { - conditions := strings.Split(expr, "&&") - for _, condition := range conditions { - condition = strings.TrimSpace(condition) - result := evaluateExpression(condition, ctx) - if !result { + parts := strings.Split(expr, "&&") + for _, part := range parts { + subExpr := strings.TrimSpace(part) + if !evaluateExpression(subExpr, ctx) { + fmt.Printf("[DEBUG] %s AND chain failed at: %q\n", formatHitMark(false), subExpr) return false } } + fmt.Printf("[DEBUG] %s All AND conditions met\n", formatHitMark(true)) return true } - // Support OR operation + // 处理 OR 操作 if strings.Contains(expr, "||") { - for _, condition := range strings.Split(expr, "||") { - if evaluateExpression(strings.TrimSpace(condition), ctx) { + parts := strings.Split(expr, "||") + for _, part := range parts { + subExpr := strings.TrimSpace(part) + if evaluateExpression(subExpr, ctx) { + fmt.Printf("[DEBUG] %s OR chain succeeded at: %q\n", formatHitMark(true), subExpr) return true } } + fmt.Printf("[DEBUG] %s No OR conditions met\n", formatHitMark(false)) return false } + // Status code equality + if strings.HasPrefix(expr, "status==") { + code, err := strconv.Atoi(strings.TrimPrefix(expr, "status==")) + if err != nil { + fmt.Printf("[DEBUG] ❌ Invalid status code format\n") + return false + } + result := ctx.StatusCode == code + fmt.Printf("[DEBUG] %s Status check: %d == %d: %v\n", + formatHitMark(result), ctx.StatusCode, code, result) + return result + } + + // Response body contains string + if strings.HasPrefix(expr, "contains(") && strings.HasSuffix(expr, ")") { + content := expr[9 : len(expr)-1] + result := strings.Contains(ctx.Body, content) + fmt.Printf("[DEBUG] %s Contains check for %q: %v\n", + formatHitMark(result), content, result) + return result + } + if strings.Contains(expr, ".bcontains(") { + fmt.Printf("[DEBUG] bcontains operation detected\n") prefix := "response.body.bcontains(b\"" - prefixstr := "response.body.bcontains(bytes(string(" suffix := "\")" - if strings.HasPrefix(expr, prefix) && strings.HasSuffix(expr, suffix) { searchStr := expr[len(prefix) : len(expr)-len(suffix)] - searchStr = strings.ReplaceAll(searchStr, `""`, `"`) - return strings.Contains(ctx.Body, searchStr) - } else if strings.HasPrefix(expr, prefixstr) && strings.HasSuffix(expr, suffix) { - varName := expr[len(prefix) : len(expr)-len(suffix)] - return strings.Contains(ctx.Body, varName) - } - } + fmt.Printf("[DEBUG] Original searchStr: %q\n", searchStr) - // Handle special bmatches syntax - if strings.Contains(expr, ".bmatches(") { - re := regexp.MustCompile(`"([^"]+)"\.bmatches\((.+)\)`) - if matches := re.FindStringSubmatch(expr); len(matches) == 3 { - pattern := matches[1] - target := ctx.Body - re, err := regexp.Compile(pattern) - if err != nil { - return false - } - return re.MatchString(target) - } - } + // 处理转义字符 + searchStr = strings.ReplaceAll(searchStr, `\\`, `\`) + searchStr = strings.ReplaceAll(searchStr, `\"`, `"`) + fmt.Printf("[DEBUG] After unescape searchStr: %q\n", searchStr) + fmt.Printf("[DEBUG] Response body: %q\n", ctx.Body) - // Handle "in" operation - if strings.Contains(expr, " in ") { - parts := strings.Split(expr, " in ") - if len(parts) == 2 { - key := strings.Trim(parts[0], "\"") - if parts[1] == "response.headers" { - _, exists := ctx.Headers[key] - return exists - } + result := strings.Contains(ctx.Body, searchStr) + fmt.Printf("[DEBUG] %s bcontains search for %q: %v\n", + formatHitMark(result), searchStr, result) + return result } } @@ -416,96 +440,51 @@ func evaluateExpression(expr string, ctx *ExprContext) bool { re := regexp.MustCompile(`response\.status\s*==\s*(\d+)`) if matches := re.FindStringSubmatch(expr); len(matches) == 2 { expectedStatus, _ := strconv.Atoi(matches[1]) - return ctx.StatusCode == expectedStatus - } - } - - // Status code equality - if strings.HasPrefix(expr, "status==") { - code, err := strconv.Atoi(strings.TrimPrefix(expr, "status==")) - if err != nil { - return false - } - return ctx.StatusCode == code - } - - // Status code inequality - if strings.HasPrefix(expr, "status!=") { - code, err := strconv.Atoi(strings.TrimPrefix(expr, "status!=")) - if err != nil { - return false + result := ctx.StatusCode == expectedStatus + fmt.Printf("[DEBUG] %s Response status check: %d == %d: %v\n", + formatHitMark(result), ctx.StatusCode, expectedStatus, result) + return result } - return ctx.StatusCode != code - } - - // Response body contains string - if strings.HasPrefix(expr, "contains(") && strings.HasSuffix(expr, ")") { - content := expr[9 : len(expr)-1] // Extract the content inside the parentheses - return strings.Contains(ctx.Body, content) - } - - // Response body does not contain string - if strings.HasPrefix(expr, "!contains(") && strings.HasSuffix(expr, ")") { - content := expr[10 : len(expr)-1] // Extract the content inside the parentheses - return !strings.Contains(ctx.Body, content) } // Response body regular expression matching if strings.HasPrefix(expr, "matches(") && strings.HasSuffix(expr, ")") { - pattern := expr[8 : len(expr)-1] // Extract the content inside the parentheses + pattern := expr[8 : len(expr)-1] re, err := regexp.Compile(pattern) if err != nil { + fmt.Printf("[DEBUG] ❌ Invalid regex pattern: %v\n", err) return false } - return re.MatchString(ctx.Body) - } - - // Response body length equality - if strings.HasPrefix(expr, "length==") { - length, err := strconv.Atoi(strings.TrimPrefix(expr, "length==")) - if err != nil { - return false - } - return len(ctx.Body) == length - } - - // Response body length greater than - if strings.HasPrefix(expr, "length>") { - length, err := strconv.Atoi(strings.TrimPrefix(expr, "length>")) - if err != nil { - return false - } - return len(ctx.Body) > length - } - - // Response body length less than - if strings.HasPrefix(expr, "length<") { - length, err := strconv.Atoi(strings.TrimPrefix(expr, "length<")) - if err != nil { - return false - } - return len(ctx.Body) < length + result := re.MatchString(ctx.Body) + fmt.Printf("[DEBUG] %s Regex match for pattern %q: %v\n", + formatHitMark(result), pattern, result) + return result } // Check if the response headers contain specific values if strings.HasPrefix(expr, "header(") && strings.HasSuffix(expr, ")") { - // Format: header(Key: Value) content := expr[7 : len(expr)-1] parts := strings.SplitN(content, ":", 2) if len(parts) != 2 { + fmt.Printf("[DEBUG] ❌ Invalid header format\n") return false } headerKey := strings.TrimSpace(parts[0]) headerValue := strings.TrimSpace(parts[1]) - return containsHeader(ctx.Headers, headerKey, headerValue) - } - if strings.Contains(expr, "response.content_type.contains(") { - re := regexp.MustCompile(`response\.content_type\.contains\("([^"]+)"\)`) - if matches := re.FindStringSubmatch(expr); len(matches) == 2 { - contentType := ctx.Headers.Get("Content-Type") - searchStr := matches[1] - return strings.Contains(strings.ToLower(contentType), strings.ToLower(searchStr)) - } + result := containsHeader(ctx.Headers, headerKey, headerValue) + fmt.Printf("[DEBUG] %s Header check %q: %q: %v\n", + formatHitMark(result), headerKey, headerValue, result) + return result } + + fmt.Printf("[DEBUG] ❌ No matching expression found for: %s\n", expr) return false } + +// 添加一个辅助函数来格式化命中标记 +func formatHitMark(hit bool) string { + if hit { + return "✅ HIT!" + } + return "❌ MISS" +} diff --git a/pkg/stage/service.go b/pkg/stage/service.go index 125e1e4..d193f5f 100644 --- a/pkg/stage/service.go +++ b/pkg/stage/service.go @@ -338,14 +338,14 @@ func (sd *ServiceDetector) checkURL(url string, port int) *ServiceInfo { defer wg.Done() for poc := range pocChan { result := sd.pocExecutor.ExecutePOC(poc, url) - - vulnMux.Lock() - if info.Vulnerabilities == nil { - info.Vulnerabilities = make([]POCResult, 0) + if result != nil { + vulnMux.Lock() + if info.Vulnerabilities == nil { + info.Vulnerabilities = make([]POCResult, 0) + } + info.Vulnerabilities = append(info.Vulnerabilities, *result) + vulnMux.Unlock() } - info.Vulnerabilities = append(info.Vulnerabilities, result) - vulnMux.Unlock() - } }() }