Skip to content

Commit

Permalink
fix: use both phases for getting FPs
Browse files Browse the repository at this point in the history
Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>
  • Loading branch information
fzipi committed Sep 22, 2024
1 parent e8707f9 commit 300ba7d
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 111 deletions.
28 changes: 14 additions & 14 deletions internal/quantitative/local_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,28 +88,27 @@ func (e *localEngine) CrsCall(payload string) (int, map[int]string) {
if e.waf == nil {
log.Fatal().Msg("local engine not initialized")
}
// we use the payload in the URI to rules in phase:1 can catch it
uri := fmt.Sprintf("/get?payload=%s", url.QueryEscape(payload))

tx := e.waf.NewTransaction()
tx.ProcessConnection("127.0.0.1", 8080, "127.0.0.1", 8080)
tx.ProcessURI("/post", "POST", "HTTP/1.1")
tx.ProcessURI(uri, "GET", "HTTP/1.1")
tx.AddRequestHeader("Host", "localhost")
tx.AddRequestHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75. 0.3770.100 Safari/537.36")
tx.AddRequestHeader("Accept", "application/json")
tx.AddRequestHeader("Content-Type", "application/x-www-form-urlencoded")
body := []byte("payload=" + url.QueryEscape(payload))

tx.AddRequestHeader("Content-Length", strconv.Itoa(len(body)))
tx.ProcessRequestHeaders()
if _, _, err := tx.WriteRequestBody(body); err != nil {
log.Error().Err(err).Msg("failed to write request body")
}
it, err := tx.ProcessRequestBody()
if err != nil {
log.Error().Err(err).Msg("failed to process request body")
tx.AddRequestHeader("Accept", "*/*")

// we need to check also for phase:1 rules only
it := tx.ProcessRequestHeaders()
if it == nil { // if no interruption, we check phase:2
it, _ = tx.ProcessRequestBody()
}
if it != nil { // execution was interrupted

if it != nil {
status = obtainStatusCodeFromInterruptionOrDefault(it, http.StatusOK)
matchedRules = getMatchedRules(tx)
}

// We don't care about the response body for now, nor logging.
if err := tx.Close(); err != nil {
log.Error().Err(err).Msg("failed to close transaction")
Expand Down Expand Up @@ -161,6 +160,7 @@ func crsWAF(prefix string, paranoiaLevel int) coraza.WAF {
}

func obtainStatusCodeFromInterruptionOrDefault(it *types.Interruption, defaultStatusCode int) int {
log.Debug().Msgf("Interruption: %s", it.Action)
if it.Action == "deny" {
statusCode := it.Status
if statusCode == 0 {
Expand Down
15 changes: 14 additions & 1 deletion internal/quantitative/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package quantitative

import (
"encoding/json"
"sort"
"time"

"github.com/rs/zerolog/log"
Expand Down Expand Up @@ -45,7 +46,19 @@ func (s *QuantitativeRunStats) printSummary(out *output.Output) {
ratio := float64(s.falsePositives) / float64(s.count_)
out.Println("Run %d payloads in %s", s.count_, s.totalTime)
out.Println("Total False positive ratio: %d/%d = %.4f", s.falsePositives, s.count_, ratio)
out.Println("False positives per rule: %+v", s.falsePositivesPerRule)
out.Println("False positives per rule id:")
// Extract and sort the keys
rules := make([]int, 0, len(s.falsePositivesPerRule))
for rule := range s.falsePositivesPerRule {
rules = append(rules, rule)
}
sort.Ints(rules)

// Print the sorted map
for _, rule := range rules {
count := s.falsePositivesPerRule[rule]
out.Println(" %d: %d false positives", rule, count)
}
}
} else {
out.Println("No false positives detected with the passed corpus")
Expand Down
192 changes: 96 additions & 96 deletions internal/quantitative/stats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,128 +4,128 @@
package quantitative

import (
"bytes"
"testing"
"time"
"bytes"
"testing"
"time"

"github.com/stretchr/testify/suite"
"github.com/stretchr/testify/suite"

"github.com/coreruleset/go-ftw/output"
"github.com/coreruleset/go-ftw/output"
)

type statsTestSuite struct {
suite.Suite
suite.Suite
}

func TestStatsTestSuite(t *testing.T) {
suite.Run(t, new(statsTestSuite))
suite.Run(t, new(statsTestSuite))
}

func (s *statsTestSuite) TestNewQuantitativeStats() {
tests := []struct {
name string
want *QuantitativeRunStats
}{
{
name: "Test 1",
want: &QuantitativeRunStats{
count_: 0,
falsePositives: 0,
falsePositivesPerRule: make(map[int]int),
totalTime: 0,
},
},
}
for _, tt := range tests {
s.Run(tt.name, func() {
got := NewQuantitativeStats()
s.Require().Equal(got, tt.want)
})
}
tests := []struct {
name string
want *QuantitativeRunStats
}{
{
name: "Test 1",
want: &QuantitativeRunStats{
count_: 0,
falsePositives: 0,
falsePositivesPerRule: make(map[int]int),
totalTime: 0,
},
},
}
for _, tt := range tests {
s.Run(tt.name, func() {
got := NewQuantitativeStats()
s.Require().Equal(got, tt.want)
})
}
}

func (s *statsTestSuite) TestQuantitativeRunStats_MarshalJSON() {
type fields struct {
count_ int
totalTime time.Duration
falsePositives int
falsePositivesPerRule map[int]int
}
tests := []struct {
name string
fields fields
want []byte
wantErr bool
}{
{
name: "Test 1",
fields: fields{
count_: 1,
totalTime: 1,
falsePositives: 1,
falsePositivesPerRule: map[int]int{920010: 1},
},
want: []byte(`{"count":1,"falsePositives":1,"falsePositivesPerRule":{"920010":1},"totalTime":1}`),
wantErr: false,
},
{
name: "Test 2",
fields: fields{
count_: 2,
totalTime: 2,
falsePositives: 2,
falsePositivesPerRule: map[int]int{933100: 2},
},
want: []byte(`{"count":2,"falsePositives":2,"falsePositivesPerRule":{"933100":2},"totalTime":2}`),
wantErr: false,
},
}
for _, tt := range tests {
s.Run(tt.name, func() {
q := &QuantitativeRunStats{
count_: tt.fields.count_,
totalTime: tt.fields.totalTime,
falsePositives: tt.fields.falsePositives,
falsePositivesPerRule: tt.fields.falsePositivesPerRule,
}
got, err := q.MarshalJSON()
s.Require().NoError(err)
s.Require().Equal(got, tt.want)
})
}
type fields struct {
count_ int
totalTime time.Duration
falsePositives int
falsePositivesPerRule map[int]int
}
tests := []struct {
name string
fields fields
want []byte
wantErr bool
}{
{
name: "Test 1",
fields: fields{
count_: 1,
totalTime: 1,
falsePositives: 1,
falsePositivesPerRule: map[int]int{920010: 1},
},
want: []byte(`{"count":1,"falsePositives":1,"falsePositivesPerRule":{"920010":1},"totalTime":1}`),
wantErr: false,
},
{
name: "Test 2",
fields: fields{
count_: 2,
totalTime: 2,
falsePositives: 2,
falsePositivesPerRule: map[int]int{933100: 2},
},
want: []byte(`{"count":2,"falsePositives":2,"falsePositivesPerRule":{"933100":2},"totalTime":2}`),
wantErr: false,
},
}
for _, tt := range tests {
s.Run(tt.name, func() {
q := &QuantitativeRunStats{
count_: tt.fields.count_,
totalTime: tt.fields.totalTime,
falsePositives: tt.fields.falsePositives,
falsePositivesPerRule: tt.fields.falsePositivesPerRule,
}
got, err := q.MarshalJSON()
s.Require().NoError(err)
s.Require().Equal(got, tt.want)
})
}
}

func (s *statsTestSuite) TestQuantitativeRunStats_functions() {
q := NewQuantitativeStats()
q := NewQuantitativeStats()

q.incrementRun()
s.Require().Equal(q.Count(), 1)
q.incrementRun()
s.Require().Equal(q.Count(), 1)

q.addFalsePositive(920100)
s.Require().Equal(q.FalsePositives(), 1)
q.addFalsePositive(920100)
s.Require().Equal(q.FalsePositives(), 1)

q.incrementRun()
s.Require().Equal(q.Count(), 2)
q.incrementRun()
s.Require().Equal(q.Count(), 2)

q.addFalsePositive(920200)
s.Require().Equal(q.FalsePositives(), 2)
q.addFalsePositive(920200)
s.Require().Equal(q.FalsePositives(), 2)

duration := time.Duration(5000)
q.SetTotalTime(duration)
s.Require().Equal(q.TotalTime(), duration)
duration := time.Duration(5000)
q.SetTotalTime(duration)
s.Require().Equal(q.TotalTime(), duration)
}

func (s *statsTestSuite) TestQuantitativeRunStats_printSummary() {
var b bytes.Buffer
out := output.NewOutput("plain", &b)
q := NewQuantitativeStats()
var b bytes.Buffer
out := output.NewOutput("plain", &b)
q := NewQuantitativeStats()

q.incrementRun()
s.Require().Equal(q.Count(), 1)
q.incrementRun()
s.Require().Equal(q.Count(), 1)

q.addFalsePositive(920100)
s.Require().Equal(q.FalsePositives(), 1)
q.addFalsePositive(920100)
s.Require().Equal(q.FalsePositives(), 1)

q.printSummary(out)
s.Require().Equal(b.String(), "Run 1 payloads in 0s\nTotal False positive ratio: 1/1 = 1.0000\nFalse positives per rule: map[920100:1]\n")
q.printSummary(out)
s.Require().Equal("Run 1 payloads in 0s\nTotal False positive ratio: 1/1 = 1.0000\nFalse positives per rule id:\n 920100: 1 false positives\n", b.String())
}

0 comments on commit 300ba7d

Please sign in to comment.