feat(waf): Request-Body-Inspektion — Coraza sieht jetzt POST/PUT-Payloads — v1.3.6

Bisher inspizierte die WAF nur URL/Querystring + Header (SPOE sendete keinen
Body, ProcessRequestBody wurde nie aufgerufen) → blind für POST/PUT-Payloads
(Form-SQLi, JSON-Injection, Uploads). Jetzt:

- haproxy.cfg.tpl: `option http-buffer-request` im public_https-Frontend, NUR
  wenn WAF aktiv (.WAFEnabled) — kein RAM-pro-Connection-Overhead sonst.
- spoeCfg (haproxy.go): SPOE-Message sendet `body=req.body` an den Agent.
- spoe.go: Body einsammeln → tx.WriteRequestBody + tx.ProcessRequestBody nach
  der Header-Phase (vor MatchedRules-Log, damit Body-Treffer geloggt werden);
  Interruption blockt in blocking-Mode.

Coraza-Engine war schon bereit (SecRequestBodyAccess On + Limits, engine.go).
Puffer bis tune.bufsize (~16KB); größere Bodies zur Prüfung gekappt.
Render-Test: http-buffer-request nur bei WAF + vor dem SPOE-Filter; body=req.body.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Debian
2026-07-31 12:27:41 +02:00
parent dca40761a4
commit 96290253c8
5 changed files with 67 additions and 2 deletions

View File

@@ -41,6 +41,7 @@ func (a *SPOEAgent) handle(ctx context.Context, w *encoding.ActionWriter, m *enc
httpVer string
host string
rawHdrs string
body []byte // gepufferter Request-Body (via HAProxy option http-buffer-request)
)
// Iterate over the key-value pairs HAProxy sent with this message.
@@ -63,6 +64,12 @@ func (a *SPOEAgent) handle(ctx context.Context, w *encoding.ActionWriter, m *enc
host = string(entry.ValueBytes())
case entry.NameEquals("headers"):
rawHdrs = string(entry.ValueBytes())
case entry.NameEquals("body"):
// Kopieren: entry wird nach Reset() wiederverwendet, der
// zugrundeliegende Puffer darf nicht referenziert bleiben.
if b := entry.ValueBytes(); len(b) > 0 {
body = append([]byte(nil), b...)
}
}
entry.Reset()
}
@@ -119,6 +126,26 @@ func (a *SPOEAgent) handle(ctx context.Context, w *encoding.ActionWriter, m *enc
// Evaluate request headers.
interruption := tx.ProcessRequestHeaders()
// Request-Body inspizieren (POST/PUT-Payloads: Form-SQLi, JSON-Injection,
// Uploads). Nur wenn die Header-Phase noch nicht geblockt hat. HAProxy
// liefert den Body via `option http-buffer-request` (bis tune.bufsize) —
// größere Bodies werden zur Prüfung gekappt. Content-Type kam bereits
// über die Header, sodass Coraza urlencoded/multipart/json korrekt parst.
if interruption == nil && len(body) > 0 {
if it, _, err := tx.WriteRequestBody(body); err != nil {
slog.Warn("waf: WriteRequestBody", "error", err)
} else if it != nil {
interruption = it
} else {
it, err := tx.ProcessRequestBody()
if err != nil {
slog.Warn("waf: ProcessRequestBody", "error", err)
} else if it != nil {
interruption = it
}
}
}
// Log all matched rules (detection + blocking).
for _, mr := range tx.MatchedRules() {
a.sendAlert(host, clientIP, method, uri, mr, interruption != nil)