Log injection turns ZooKeeper audit records into attacker-controlled text
Log injection turns ZooKeeper audit records into attacker-controlled text
wo Apache ZooKeeper advisories from 16 September 2026 carry only a medium severity rating, and both attack the same asset: the integrity of the logs an investigator will read afterwards.
CVE-2026-84439 and CVE-2026-84501 are rated 5.3 with a CWE-117 classification in the NVD record. Their impact is not remote code execution or data theft. It is that a record of activity in a coordination service can be made to say something false, at a moment when the record is the only evidence available.
Audit log field injection
CVE-2026-84439 targets zookeeper_audit.log when audit logging is enabled through zookeeper.audit.enable=true. The audit writer emits tab-separated key=value records, and it writes attacker-controlled digest identifiers into the acl= field without escaping tab characters.
The advisory describes the outcome precisely. A crafted digest ACL identifier containing tabs is parsed by downstream consumers as additional field separators. The attacker can inject a forged result=success, forge an operation= value, and forge a znode= path, making one real event appear to contain fields that were never recorded.
The advisory also names a second route: a client able to call setACL can inject forged fields through the same unescaped serialisation.
The advisory also describes an unauthenticated route through a digest authentication request. Both that route and the setACL route depend on somebody building a request with embedded tabs, and they differ in the access they require.
Operational log line injection
CVE-2026-84501 does the same thing to the operational log, without requiring audit logging to be enabled. A crafted add_auth("ensemble", ...) request carrying newline characters in the ensemble name reaches EnsembleAuthenticationProvider.handleAuthentication(). When the name does not match, the server logs the raw value through LOG.warn().
SLF4J's {} placeholder substitutes the value without sanitising embedded newlines, so the attacker supplies complete log lines with their own timestamps, levels, class names and messages. The advisory's phrasing is direct: the forged output is visually indistinguishable from genuine ZooKeeper output.
Both issues affect ZooKeeper 3.9.0 through 3.9.5 and 3.8.0 through 3.8.6, and both are fixed in 3.9.6 and 3.8.7.
Why a log can be a control, not an artefact
The severity rating invites a shrug. The CVE is a text handling defect, nothing is compromised, and the fix is a version bump. That view assumes logs are read by a person who will notice something odd.
Modern detection pipelines do not read logs that way. A parser splits the audit record on tabs, maps result to a field, and an alert fires on failed authentication. If the attacker can write result=success into a record for a failed login, the alert does not fire. The evidence still exists; it has been reframed as evidence of the opposite.
Injected lines in the operational log reach the same pipelines differently. A fabricated line with a plausible timestamp can place an event outside the window a query covers, or create an event that never happened and send an analyst down a path that costs hours.
ZooKeeper amplifies both cases because the systems it coordinates log elsewhere. HDFS, HBase, Kafka, and a long list of schedulers and registries write their own records about connections to the ensemble, and those records rarely carry the detail needed to contradict a falsified ZooKeeper entry.
Detection depends on parsing assumptions
If your pipeline split audit records on tabs, this CVE is about your pipeline as much as about ZooKeeper. The defensible controls are unglamorous.
Parse defensively. Tab-separated key=value logs are convenient and easy to forge. A parser that rejects records where a field count differs from the expected schema, or where a field name appears twice, catches this class of injection without needing to understand ZooKeeper.
Watch for structural anomalies rather than content. An audit record whose acl= value contains a tab is malformed by construction. So is an operational log line whose message contains a newline followed by a plausible SLF4J prefix.
Correlate client-side and server-side views. The injected operational line does not correspond to a connection in any network flow record, because the connection that generated it held a different peer address. A join on peer address between the log line and the flow record breaks the forgery, though it requires that flows are retained.
Checks an operator can run
Confirm the version. Both issues span 3.9.0 to 3.9.5 and 3.8.0 to 3.8.6 and are fixed in 3.9.6 and 3.8.7. echo srvr | nc <host> 2181 returns the running version when the four-letter-word endpoint is enabled, which is a separate exposure worth narrowing through zookeeper.4lw.commands.whitelist.
Check whether audit logging is on. The first issue depends on zookeeper.audit.enable=true. A deployment without audit logging is not exposed to that specific injection, and it also has no audit trail, which is a different conversation with the same owner.
Review the downstream parser. This is where the fix has real work. If the pipeline splits on tabs and maps the first occurrence of each key, injection succeeds on an unpatched server and, depending on the parser, may also succeed on a patched one that is fed historical data.
Retain and hash. Audit logs shipped to an append-only store with per-batch hashes make later tampering detectable. The injection happens before the log is written, so hashing does not prevent it. It does make the difference between "the log says this" and "the log always said this" a question with an answer.
The residual risk after patching
Releases 3.9.6 and 3.8.7 escape the characters that cause the injection. They do not make the audit log trustworthy in the broader sense, and they cannot repair entries already written before the upgrade.
Two things follow. Historical audit data from an affected version should be treated as unverified when it is used as evidence, with timestamps near a suspected compromise examined against an independent source. And the detection work above remains useful after patching, because unsanitised logging is a recurring defect class in every project, not a ZooKeeper speciality.
Apache published both issues on the same day as two ACL defects in the same releases. An operator planning the upgrade gets all four fixed in one window, which is the practical argument for scheduling it as a single change rather than four.
References
- Apache ZooKeeper advisory for CVE-2026-84439: https://lists.apache.org/thread/b7qnjvqjh393l0j07tmnb5ggg40sx3m6
- Openwall mirror for CVE-2026-84439: http://www.openwall.com/lists/oss-security/2026/09/15/6
- Apache ZooKeeper advisory for CVE-2026-84501: https://lists.apache.org/thread/ohby1g6zjt72p0w79rhbr41nczzvr33y
- Openwall mirror for CVE-2026-84501: http://www.openwall.com/lists/oss-security/2026/09/15/7
- NVD records: https://nvd.nist.gov/vuln/detail/CVE-2026-84439, https://nvd.nist.gov/vuln/detail/CVE-2026-84501
Did you enjoy this article?
Recommend it — Standard Reader surfaces well-loved writing to more readers across the network.