Files
edgeguard-native/internal/waf/spoe_phase2_test.go
Debian de153dc13a fix(waf): GET-Requests ohne Body wurden von allen Phase-2-Regeln nie geprüft — v1.3.18
Schwerwiegende WAF-Lücke: der SPOE-Agent rief tx.ProcessRequestBody() nur bei
len(body)>0 auf. In Coraza wird die GESAMTE Phase 2 (SQLi 942xxx, XSS 941xxx,
LFI/RCE — alle prüfen ARGS aus dem Query-String) aber erst von
ProcessRequestBody() ausgewertet. Damit lief jeder GET-Request ohne Body
komplett ungeprüft an den Injection-Regeln vorbei — der häufigste
Web-Angriffsvektor (?id=1' OR 1=1, ?x=<script>) war blind. Nur Requests MIT
Body (POST/PUT/PROPFIND-XML) wurden inspiziert.

Fix: ProcessRequestBody() läuft jetzt IMMER (mit/ohne Body). Regressionstest
TestPhase2RequiresProcessRequestBody nagelt die Semantik fest.

Alle 9 aktiven WAF-Domains sind in Detection-Modus → der Fix erzeugt nur mehr
(echte) Alerts, blockt nichts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-04 10:00:17 +02:00

51 lines
1.7 KiB
Go

package waf
import (
"testing"
"github.com/corazawaf/coraza/v3"
)
// TestPhase2RequiresProcessRequestBody nagelt die Coraza-Semantik fest, die der
// SPOE-Bug verletzt hatte: Phase-2-Regeln (SQLi 942xxx, XSS 941xxx — sie prüfen
// ARGS aus dem Query-String) werden ERST von ProcessRequestBody() ausgewertet.
// Koppelt man ProcessRequestBody an len(body)>0, bleiben GET-Requests ohne Body
// von der gesamten Phase 2 ungeprüft. Dieser Test schlägt fehl, sollte jemand
// den Aufruf je wieder body-abhängig machen.
func TestPhase2RequiresProcessRequestBody(t *testing.T) {
waf, err := coraza.NewWAF(coraza.NewWAFConfig().WithDirectives(
"SecRuleEngine DetectionOnly\n" +
"SecRule ARGS \"@rx attackpattern\" \"id:1234,phase:2,log,msg:'phase2-arg'\"\n"))
if err != nil {
t.Fatalf("NewWAF: %v", err)
}
tx := waf.NewTransaction()
defer func() { tx.ProcessLogging(); _ = tx.Close() }()
tx.ProcessConnection("1.2.3.4", 0, "", 0)
tx.ProcessURI("/?x=attackpattern", "GET", "HTTP/1.1") // GET, kein Body
tx.AddRequestHeader("Host", "test")
tx.ProcessRequestHeaders()
// Vor ProcessRequestBody darf die Phase-2-Regel noch NICHT gefeuert haben.
if n := len(tx.MatchedRules()); n != 0 {
t.Fatalf("vor ProcessRequestBody: %d Matches, erwarte 0", n)
}
if _, err := tx.ProcessRequestBody(); err != nil {
t.Fatalf("ProcessRequestBody: %v", err)
}
// Jetzt MUSS die Phase-2-Regel gegen den Query-Arg gefeuert haben.
found := false
for _, mr := range tx.MatchedRules() {
if mr.Rule().ID() == 1234 {
found = true
}
}
if !found {
t.Fatal("Phase-2-Regel feuerte auch nach ProcessRequestBody nicht — " +
"GET-Query-Args würden ungeprüft bleiben (SPOE-Bug)")
}
}