Skip to content

Commit

Permalink
fix: enhance debug logging for bcontains expression evaluation
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Your Name committed Nov 22, 2024
1 parent 066ba9c commit b905707
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 127 deletions.
4 changes: 2 additions & 2 deletions config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ tcp_ports:
- 7080 # Generic
- 7080 # Generic
- 7680 # Windows Update Delivery Optimization
- 7788
- 8000
- 8001 # HTTP Alternate
- 8002 # HTTP Alternate
Expand All @@ -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
Expand Down
10 changes: 8 additions & 2 deletions pkg/stage/assets/fingerprints.json
Original file line number Diff line number Diff line change
Expand Up @@ -1369,8 +1369,14 @@
"body":[
"视频编码设备接入网关"
]
}
},
"ifw8-router":{
"type": "ipcamera",
"manufacturer": "ifw8",
"body": [
"(?i)www.ifw8.cn"
]
}
}



211 changes: 95 additions & 116 deletions pkg/stage/poc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
}

Expand All @@ -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
}

Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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
}
}

Expand All @@ -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"
}
14 changes: 7 additions & 7 deletions pkg/stage/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

}
}()
}
Expand Down

0 comments on commit b905707

Please sign in to comment.