Skip to content

Commit 883c3e5

Browse files
authored
[WAF] do not iterate over all transaction variables for nothing // swap alert generation + event send order (#3884)
1 parent 235ad66 commit 883c3e5

File tree

4 files changed

+60
-46
lines changed

4 files changed

+60
-46
lines changed

pkg/acquisition/modules/appsec/appsec_hooks_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -361,10 +361,10 @@ func TestAppsecOnMatchHooks(t *testing.T) {
361361
require.Equal(t, http.StatusOK, statusCode)
362362
// We have both an event an overflow
363363
require.Len(t, events, 2)
364-
require.Equal(t, types.LOG, events[0].Type)
365-
require.Equal(t, types.APPSEC, events[1].Type)
366-
require.Nil(t, events[0].Overflow.Alert)
367-
require.NotNil(t, events[1].Overflow.Alert)
364+
require.Equal(t, types.APPSEC, events[0].Type)
365+
require.Equal(t, types.LOG, events[1].Type)
366+
require.Nil(t, events[1].Overflow.Alert)
367+
require.NotNil(t, events[0].Overflow.Alert)
368368
},
369369
},
370370
{

pkg/acquisition/modules/appsec/appsec_runner.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -320,11 +320,10 @@ func (r *AppsecRunner) handleOutBandInterrupt(request *appsec.ParsedRequest) {
320320
r.logger.Errorf("unable to process OnMatch rules: %s", err)
321321
return
322322
}
323-
// Should the match trigger an event ?
324-
if r.AppsecRuntime.Response.SendEvent {
325-
r.outChan <- evt
326-
}
327323

324+
// The alert needs to be sent first:
325+
// The event and the alert share the same internal map (parsed, meta, ...)
326+
// The event can be modified by the parsers, which might cause a concurrent map read/write
328327
// Should the match trigger an overflow ?
329328
if r.AppsecRuntime.Response.SendAlert {
330329
appsecOvlfw, err := AppsecEventGeneration(evt, request.HTTPRequest)
@@ -336,6 +335,11 @@ func (r *AppsecRunner) handleOutBandInterrupt(request *appsec.ParsedRequest) {
336335
r.outChan <- *appsecOvlfw
337336
}
338337
}
338+
339+
// Should the match trigger an event ?
340+
if r.AppsecRuntime.Response.SendEvent {
341+
r.outChan <- evt
342+
}
339343
}
340344
}
341345

pkg/acquisition/modules/appsec/utils.go

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"net"
77
"net/http"
8+
"slices"
89
"strings"
910
"time"
1011

@@ -34,6 +35,18 @@ var excludedMatchCollections = []string{
3435
"TX", // Score has been exceeded
3536
}
3637

38+
var CRSAnomalyScores = []string{
39+
"sql_injection_score",
40+
"xss_score",
41+
"rfi_score",
42+
"lfi_score",
43+
"rce_score",
44+
"php_injection_score",
45+
"http_violation_score",
46+
"session_fixation_score",
47+
"anomaly_score",
48+
}
49+
3750
func AppsecEventGenerationGeoIPEnrich(src *models.Source) error {
3851

3952
if src == nil || src.Scope == nil || *src.Scope != types.Ip {
@@ -79,9 +92,9 @@ func formatCRSMatch(vars map[string]string, hasInBandMatches bool, hasOutBandMat
7992
case hasOutBandMatches:
8093
msg += "out-of-band: "
8194
}
82-
for _, var_name := range appsec.CRSAnomalyScores {
83-
if val, ok := vars[var_name]; ok && val != "0" {
84-
msg += fmt.Sprintf("%s: %s, ", strings.Replace(strings.Replace(var_name, "TX.", "", 1), "_score", "", 1), val)
95+
for _, var_name := range CRSAnomalyScores {
96+
if val, ok := vars["TX."+var_name]; ok && val != "0" {
97+
msg += fmt.Sprintf("%s: %s, ", strings.Replace(var_name, "_score", "", 1), val)
8598
}
8699
}
87100
return msg
@@ -349,31 +362,43 @@ func (r *AppsecRunner) AccumulateTxToEvent(evt *types.Event, req *appsec.ParsedR
349362
evt.Appsec.Vars = map[string]string{}
350363
}
351364

352-
req.Tx.Variables().All(func(v variables.RuleVariable, col collection.Collection) bool {
353-
for _, variable := range col.FindAll() {
354-
r.logger.Tracef("variable: %s.%s = %s\n", variable.Variable().Name(), variable.Key(), variable.Value())
355-
key := variable.Variable().Name()
356-
if variable.Key() != "" {
357-
key += "." + variable.Key()
358-
}
365+
txCollection := req.Tx.Variables().TX()
359366

360-
if variable.Value() == "" {
361-
continue
362-
}
367+
txMatchedData := txCollection.FindAll()
368+
369+
for _, match := range txMatchedData {
370+
if slices.Contains(CRSAnomalyScores, match.Key()) {
371+
evt.Appsec.Vars["TX."+match.Key()] = match.Value()
372+
}
373+
}
363374

364-
for _, collectionToKeep := range r.AppsecRuntime.CompiledVariablesTracking {
365-
match := collectionToKeep.MatchString(key)
366-
if match {
367-
evt.Appsec.Vars[key] = variable.Value()
368-
r.logger.Debugf("%s.%s = %s", variable.Variable().Name(), variable.Key(), variable.Value())
369-
} else {
370-
r.logger.Debugf("%s.%s != %s (%s) (not kept)", variable.Variable().Name(), variable.Key(), collectionToKeep, variable.Value())
375+
if len(r.AppsecRuntime.CompiledVariablesTracking) > 0 {
376+
req.Tx.Variables().All(func(v variables.RuleVariable, col collection.Collection) bool {
377+
for _, variable := range col.FindAll() {
378+
r.logger.Tracef("variable: %s.%s = %s\n", variable.Variable().Name(), variable.Key(), variable.Value())
379+
key := variable.Variable().Name()
380+
if variable.Key() != "" {
381+
key += "." + variable.Key()
382+
}
383+
384+
if variable.Value() == "" {
385+
continue
386+
}
387+
388+
for _, collectionToKeep := range r.AppsecRuntime.CompiledVariablesTracking {
389+
match := collectionToKeep.MatchString(key)
390+
if match {
391+
evt.Appsec.Vars[key] = variable.Value()
392+
r.logger.Debugf("%s.%s = %s", variable.Variable().Name(), variable.Key(), variable.Value())
393+
} else {
394+
r.logger.Debugf("%s.%s != %s (%s) (not kept)", variable.Variable().Name(), variable.Key(), collectionToKeep, variable.Value())
395+
}
371396
}
372397
}
373-
}
374398

375-
return true
376-
})
399+
return true
400+
})
401+
}
377402

378403
for _, rule := range req.Tx.MatchedRules() {
379404
// Drop the rule if it has no message (it's likely a CRS setup rule)

pkg/appsec/appsec.go

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,6 @@ const (
3838
AllowRemediation = "allow"
3939
)
4040

41-
var CRSAnomalyScores = []string{
42-
"TX.sql_injection_score",
43-
"TX.xss_score",
44-
"TX.rfi_score",
45-
"TX.lfi_score",
46-
"TX.rce_score",
47-
"TX.php_injection_score",
48-
"TX.http_violation_score",
49-
"TX.session_fixation_score",
50-
"TX.anomaly_score",
51-
}
52-
5341
func (h *Hook) Build(hookStage int) error {
5442
ctx := map[string]interface{}{}
5543

@@ -227,9 +215,6 @@ func (wc *AppsecConfig) LoadByPath(file string) error {
227215
wc.VariablesTracking = append(wc.VariablesTracking, tmp.VariablesTracking...)
228216
}
229217

230-
// In all cases, track the CRS anomaly scores
231-
wc.VariablesTracking = append(wc.VariablesTracking, CRSAnomalyScores...)
232-
233218
// override other options
234219
wc.LogLevel = tmp.LogLevel
235220

0 commit comments

Comments
 (0)