Skip to content

Commit 5936b38

Browse files
committed
Fix heuristics detection issues with false positives
1 parent fdbc8e7 commit 5936b38

File tree

2 files changed

+66
-10
lines changed

2 files changed

+66
-10
lines changed

evaluations.go

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,34 +16,34 @@ func (r *Rule) evaluate(resp Response, urlInjection UrlInjection, ruleName strin
1616
lengthExpected := false
1717
heuristicsExpected := map[string]bool{"responsecode": false, "responselength": false, "responsecontent": false, "responseheader": false}
1818

19+
for _, match := range r.Heuristics.BaselineMatches {
20+
heuristicsExpected[strings.ToLower(match)] = true
21+
}
22+
1923
numOfChecks := 0
2024

2125
var ruleEvaluation RuleEvaluation
2226

23-
if r.Expectation.Headers != nil {
27+
if r.Expectation.Headers != nil || heuristicsExpected["responseheader"] {
2428
headersExpected = true
2529
numOfChecks += 1
2630
}
2731

28-
if r.Expectation.Contents != nil {
32+
if r.Expectation.Contents != nil || heuristicsExpected["responsecontent"] {
2933
bodyExpected = true
3034
numOfChecks += 1
3135
}
3236

33-
if r.Expectation.Codes != nil {
37+
if r.Expectation.Codes != nil || heuristicsExpected["responsecode"] {
3438
codeExpected = true
3539
numOfChecks += 1
3640
}
3741

38-
if r.Expectation.Lengths != nil {
42+
if r.Expectation.Lengths != nil || heuristicsExpected["responselength"] {
3943
lengthExpected = true
4044
numOfChecks += 1
4145
}
4246

43-
for _, match := range r.Heuristics.BaselineMatches {
44-
heuristicsExpected[strings.ToLower(match)] = true
45-
}
46-
4747
if bodyExpected {
4848
if matched := r.evaluateContent(resp.Body, heuristicsResponse, baselineResponse, heuristicsExpected["responsecontent"]); matched {
4949
ruleEvaluation.ChecksMatched += 1
@@ -92,13 +92,29 @@ func (r *Rule) evaluate(resp Response, urlInjection UrlInjection, ruleName strin
9292
}
9393

9494
func (r *Rule) evaluateContent(responseContent string, heuristicsResponse Response, baselineResponse Response, heuristicExpected bool) bool {
95+
if heuristicExpected && len(r.Expectation.Contents) == 0 {
96+
if heuristicsResponse.Body == baselineResponse.Body {
97+
// This is a false positive. If the heuristics response, baseline response, and injected response all have the same response content
98+
// It is not an indication of vulnerable functionality
99+
if baselineResponse.Body == responseContent {
100+
return false
101+
}
102+
return true
103+
}
104+
}
105+
95106
for _, content := range r.Expectation.Contents {
96107
if strings.Contains(strings.ToLower(responseContent), strings.ToLower(content)) {
97108
if !heuristicExpected {
98109
return true
99110
}
100111

101112
if heuristicsResponse.Body == baselineResponse.Body {
113+
// This is a false positive. If the heuristics response, baseline response, and injected response all have the same response content
114+
// It is not an indication of vulnerable functionality
115+
if baselineResponse.Body == responseContent {
116+
return false
117+
}
102118
return true
103119
}
104120
}
@@ -122,6 +138,17 @@ func (r *Rule) evaluateHeaders(responseHeaders http.Header, heuristicsResponse R
122138
}
123139

124140
func (r *Rule) evaluateStatusCode(responseCode int, heuristicsResponse Response, baselineResponse Response, heuristicExpected bool) bool {
141+
if heuristicExpected && len(r.Expectation.Codes) == 0 {
142+
if heuristicsResponse.StatusCode == baselineResponse.StatusCode {
143+
// This is a false positive. If the heuristics response, baseline response, and injected response all have the same code
144+
// It is not an indication of vulnerable functionality
145+
if baselineResponse.StatusCode == responseCode {
146+
return false
147+
}
148+
return true
149+
}
150+
}
151+
125152
for _, code := range r.Expectation.Codes {
126153
statusCode, err := strconv.Atoi(code)
127154
if err != nil {
@@ -147,6 +174,23 @@ func (r *Rule) evaluateStatusCode(responseCode int, heuristicsResponse Response,
147174
}
148175

149176
func (r *Rule) evaluateContentLength(responseLength int, heuristicsResponse Response, baselineResponse Response, heuristicExpected bool) bool {
177+
if heuristicExpected && len(r.Expectation.Lengths) == 0 {
178+
if heuristicsMatch := isLengthWithinTenPercent(heuristicsResponse.ContentLength, baselineResponse.ContentLength); heuristicsMatch {
179+
180+
// This is a false positive. If the heuristics response, baseline response, and injected response all have the same length
181+
// It is not an indication of vulnerable functionality
182+
if heuristicsMatch := isLengthWithinTenPercent(baselineResponse.ContentLength, responseLength); heuristicsMatch {
183+
fmt.Println(baselineResponse.ContentLength)
184+
fmt.Println(responseLength)
185+
fmt.Println(heuristicsResponse.ContentLength)
186+
187+
return false
188+
}
189+
190+
return true
191+
}
192+
}
193+
150194
for _, length := range r.Expectation.Lengths {
151195
expectedLength, err := strconv.Atoi(length)
152196
if err != nil {
@@ -157,10 +201,16 @@ func (r *Rule) evaluateContentLength(responseLength int, heuristicsResponse Resp
157201
if !heuristicExpected {
158202
return true
159203
}
204+
}
160205

161-
if heuristicsMatch := isLengthWithinTenPercent(heuristicsResponse.ContentLength, baselineResponse.ContentLength); heuristicsMatch {
162-
return true
206+
if heuristicsMatch := isLengthWithinTenPercent(heuristicsResponse.ContentLength, baselineResponse.ContentLength); heuristicsMatch {
207+
// This is a false positive. If the heuristics response, baseline response, and injected response all have the same length
208+
// It is not an indication of vulnerable functionality
209+
if heuristicsMatch := isLengthWithinTenPercent(baselineResponse.ContentLength, responseLength); heuristicsMatch {
210+
return false
163211
}
212+
213+
return true
164214
}
165215
}
166216
return false

utils.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,13 @@ func getInjectedQueryString(injectedQs url.Values) (string, error) {
160160
}
161161

162162
func isLengthWithinTenPercent(expectedLength int, responseLength int) bool {
163+
// Cannot divide by 0 if empty response
164+
if responseLength == 0 {
165+
return false
166+
}
167+
163168
diff := int(math.Abs(float64(expectedLength) - float64(responseLength)))
169+
164170
// Check if the diff is less than 10%, if so, consider a positive match
165171
if (diff/responseLength)*100 <= 10 {
166172
return true

0 commit comments

Comments
 (0)