Skip to content

CEF escaping

CEF escaping — the injection guarantee

CEF is the security-critical formatter because a CEF record is a single syslog line with structural delimiters:

CEF:0|Vendor|Product|Version|SignatureId|Name|Severity|key=value key2=value2

The header is pipe-delimited; the extension is space-separated key=value pairs. So attacker-influenced data flowing into any field could, if unescaped:

  • forge a header field with a |,
  • forge an extension key with a =, or
  • inject a whole second event with a newline that terminates the syslog line.

All CEF escaping is isolated in Cbox\Siem\Support\CefEscaper, so the security-critical logic is small and tested on its own.

The rules

Two contexts, per the current Micro Focus CEF specification:

Context Escaped Deliberately not escaped
Header fields \\\, |\|, CR/LF neutralized = (not special in the header)
Extension values \\\, =\=, CR/LF neutralized | (not special in the extension)

Three properties make this correct rather than merely plausible:

  1. Backslash is escaped first. Every method doubles \ before introducing any other escape, so the backslashes it adds for \|, \=, \r, and \n are never re-escaped into garbage.
  2. Newline neutralization is unconditional. A raw CR or LF is replaced by its two-character literal (\r / \n) in both contexts, in every code path. There is no constructor flag or config switch that can disable it — a raw 0x0A or 0x0D is never emitted in any field. This is a deliberate response to the class of bug where a configuration toggle silently re-opens newline injection.
  3. The modern extension rule is used. Backslash is escaped in extension values (per current Micro Focus guidance). The deprecated 2006 ArcSight whitepaper that said not to is not followed.

The adversarial proof

tests/Unit/CefFormatterTest.php builds an event whose action, message, and a context value each contain every metacharacter at once — a|b=c\d\r\nfoo=bar (a pipe, an equals, a backslash, and a CR/LF) — formats it, and asserts:

  • exact escaped output — the full CEF string matches byte-for-byte, so the escaping is pinned, not just "looks fine";
  • (a) stable header field count — splitting on unescaped pipes still yields the seven header fields, the severity is still in its slot, and the signature and name fields round-trip back to the original payload. The injected | did not forge a field;
  • (b) stable extension key set — the extension still parses to exactly the emitted keys (rt, cat, act, outcome, externalId, msg, cbox_note). The injected foo=bar did not become an eighth key, because its = was escaped inside the value;
  • (c) no raw newline — the record contains no \r or \n byte, so it cannot inject a second CEF/syslog line.

If any escaping rule regresses, at least one of these assertions fails.

Scope reminder

This guarantee is about formatting. Once a record is a string, delivering it safely — SSRF-guarded egress, TLS, auth — is the wrapper's job, not this package's. See the security overview for the boundary.