Vulnerability GHSA-prpw-wwv7-xjjr

Medium Risk
MEDIUM RISK
CVSS Score: 5.8
Score Range: 4.0–6.9
Medium severity vulnerabilities (CVSS 4.0–6.9). Important issues that meaningfully reduce security confidence.
4 hours ago
October 06, 2026 at 08:37 PM UTC
Coraza: Native audit-log format allows CRLF injection and log forgery via request body and header fields
v3.0.0 - v3.7.0
v3.0.0 - v3.7.0

Summary

Coraza: Native audit-log format allows CRLF injection and log forgery via request body and header fields

Details

Root Cause

File: internal/auditlog/formats.go — multiple sites write attacker-influenced bytes into the Native audit-log stream without escaping \r or \n:

// Part B — request headers (lines 72–80)
for k, vv := range al.Transaction().Request().Headers() {
    for _, v := range vv {
        res.WriteByte('\n')
        res.WriteString(k)
        res.WriteString(": ")
        res.WriteString(v)   // ← raw
    }
}

// Part C — request body (lines 85–86)
if body := al.Transaction().Request().Body(); body != "" {
    res.WriteString(body)    // ← raw
    res.WriteByte('\n')
}

// Part E — response body (lines 93–94)      raw
// Part F — response headers (lines 111–118) raw
// Part H — error messages (line 125)        raw
// Part K — matched-rule raw data (line 151)  raw

The Native format's section structure is line-based: sections are delimited by lines of the form --<10-char-random-prefix>-<Part>--, and line-based log parsers / SIEM rules rely on that structure. Any attacker-controlled bytes containing \n break the structural invariant and allow the attacker to inject lines that look like genuine audit content.

The other two Native-format implementations in Coraza are not affected: the JSON formatter (formats_json.go) and the OCSF formatter both round-trip values through json.Marshal, which escapes \r and \n.

Impact

An attacker who can land bytes into any of the listed audit-log fields can inject arbitrary lines — including lines that visually resemble new log entries — into the audit log file of a defender running the default SecAuditLogFormat Native configuration. Realistic consequences:

  • Forging entries to shift attribution. An injected line such as [client "9.9.9.9"] Coraza: Warning. ... sits alongside genuine matches in Part H, and a human operator (or simple SIEM rule) reading the log cannot tell them apart.
  • Confusing SIEM correlation. Any ingestion pipeline that splits on --...-[A-Z]-- boundaries or on [client "..."] patterns without validating the session prefix will treat the forged lines as separate records.
  • Breaking log-parsing tooling. Grep/awk pipelines, log tailers, and log-rotation tools with line-based assumptions can be poisoned with crafted binary sequences.
  • Hiding genuine incidents. An attacker who can also trigger a rule match on the same transaction (trivial — send any request that matches any audit-logged rule) can bury the real match under noise they control.

The forged lines cannot trivially impersonate an entire separate session: the 10-char random prefix in the real boundaries (boundaryPrefix := "--" + utils.RandomString(10) + "-", line 42) is not predictable from outside, and each transaction uses a fresh prefix. But the integrity of a single record is fully compromised, which is enough for the SIEM-confusion and attribution-shifting attacks.

Proof of Concept

Server with coraza.conf-recommended-style defaults:

SecRuleEngine On
SecAuditEngine On
SecAuditLogParts ABCFHZ
SecAuditLogType Serial
SecAuditLog /tmp/audit.log
SecAuditLogFormat Native
SecRequestBodyAccess On
SecRule REQUEST_METHOD "@rx ." \
    "id:1001,phase:1,pass,log,auditlog,msg:'trigger'"

Body vector — reachable via stock coraza/v3/http + net/http

Send an ordinary urlencoded POST whose body contains raw CRLF sequences and forged boundaries:

POST / HTTP/1.1
Content-Type: application/x-www-form-urlencoded

evil=benign\r\n--coraza-forged-X--\r\nForgedLine: yes\r\n--coraza-forged-H--\r\n[client "9.9.9.9"] FAKE ATTACK ENTRY

Resulting audit.log:

--heLNtylvjY-C--
evil=benign
--coraza-forged-X--
ForgedLine: yes
--coraza-forged-H--
[client "9.9.9.9"] FAKE ATTACK ENTRY

--heLNtylvjY-F--

The forged --coraza-forged-X-- / --coraza-forged-H-- boundaries and the spoofed [client "9.9.9.9"] line are structurally indistinguishable from the surrounding genuine log content. No rule fires, no error is raised, the attack is invisible to the WAF.

Header vector — reachable via non-net/http integrations only

The same effect applies to Part B (request headers) and Part F (response headers) when a header value contains raw \r\n. Go's net/http rejects such headers at parse time (400 Bad Request), so the stock HTTP wrapper is safe from this path; the vector is reachable when Coraza is called with header values that were not validated by net/http:

  • coraza-spoa (HAProxy SPOP agent) forwards headers from HAProxy, which has more permissive validation.
  • coraza-proxy-wasm / Envoy WASM hosts forward header values from the upstream proxy.
  • Custom FFI/WASM hosts and any embedder calling tx.AddRequestHeader(k, v) with unvalidated bytes.

Other raw-write sites (Part E response body, Part H error messages, Part K matched-rule data) share the same class of issue and should be fixed together.

Mitigation

Escape \r and \n at every raw-write site in internal/auditlog/formats.go. A single package-level helper is sufficient:

var logEscaper = strings.NewReplacer("\r", "\\r", "\n", "\\n")

// Part B — header values:
res.WriteString(logEscaper.Replace(v))

// Part F — header values: same
// Part H — error messages:
res.WriteString(logEscaper.Replace(alWithErrMsg.ErrorMessage()))
// Part K — matched-rule raw data:
res.WriteString(logEscaper.Replace(alEntry.Data().Raw()))

For Part C / Part E (bodies), the choice is policy-dependent:

  • Escape inline (logEscaper.Replace(body)): keeps the log human-readable for text bodies but loses fidelity for binary.
  • Base64 / hex-encode the whole part: binary-safe, matches the spirit of ModSecurity v2's binary-log handling, but less human-readable.

Escaping is the minimum; base64 for bodies is the more conservative default and is a reasonable audit-log-default change.

Additional defensive measure

Consider lengthening the boundaryPrefix random suffix from 10 chars to ≥16 chars (line 42). This strictly raises the bar for attackers attempting to fully forge a separate-looking session (not just inject lines into the current one). Low-cost change; narrows future variants of this bug class.

Affected versions

The Native formatter has been present since v3.0.0 (file existed at the first-release commit). All releases >= 3.0.0, <= 3.7.0 are affected when SecAuditLogFormat Native is used with SecAuditLogType Serial or SecAuditLogType Concurrent.

Unaffected:

  • Deployments using SecAuditLogFormat JSON (formats_json.go uses json.Marshal which escapes \r\n).
  • Deployments using OCSF output.
  • Deployments with SecAuditEngine Off.

References

  • internal/auditlog/formats.go lines 42, 72–80 (Part B), 85–88 (Part C), 93–96 (Part E), 111–118 (Part F), 125 (Part H), 151 (Part K)
  • coraza.conf-recommended — default SecAuditLogFormat Native, SecAuditLogParts ABIJDEFHZ / ABCFHZ variants
  • CWE-117 — Improper Output Neutralization for Logs
  • CWE-93 — CRLF Injection

Timeline

Published
4 hours ago
October 06, 2026 at 08:37 PM UTC
Last Modified
4 hours ago
October 06, 2026 at 08:45 PM UTC