On its face, VMSA-2026-0006 reads like most VMware advisories: a version number, a table of affected products, two CVE identifiers with a CVSS score attached. What made this one worth stopping for is that both entries score 9.8, both are described as reachable without credentials, and one of them was already flagged as under active in-the-wild exploitation before we opened a single file. At the time we started, no public technical write-up existed for either bug; just the advisory text and a patched build number to compare against.
So we did what the advisory couldn’t tell us: we diffed the patch.
The diff pipeline (1/3)
The advisory tells you what, not where
| Advisory | VMSA-2026-0006 |
| CVE-2026-59309 | VMware Directory Service, authentication bypass · CVSS 9.8 |
| CVE-2026-59310 | vCenter Syslog receiver, directory traversal → arbitrary code execution · CVSS 9.8 |
| Affected | vCenter Server Appliance (vCSA) 8.0.3.00900 (build 15564605) and earlier 8.0.3.x |
| Fixed in | 8.0.3.01000 (build 15566761) |
Two critical, pre-auth bugs, fixed in the same release, with a build number as the only visible artifact of the fix. Everything else (the vulnerable function, the missing check, the exact reachable code path) has to be recovered from the patch itself.
Why RPM-diffing alone gets you nowhere
The instinct is to diff package lists between the two patch ISOs and look for anything that changed. That instinct dies fast here.
A single build bump moves 63 of 538 RPMs in lockstep: every VMware-* package gets rebuilt, whether or not it contains a fix. Two unrelated security patches are filed under one version number, and the RPM list alone can’t tell you which of the 63 actually changed for a reason.
8.0.3.01000 is a full product build, not a targeted hotfix. 63 of 538 RPMs differ by filename between 8.0.3.00900 and 8.0.3.01000, every VMware-owned package bumped together, which is normal for a quarterly-style build train and tells us nothing about where either fix actually lives. Filename or version diffing stops at “these 63 packages are different,” which is a list of suspects, not a location.
The one thing it does hand us for free: rsyslog-8.2306.0-3.ph4.x86_64.rpm, the open-source rsyslog daemon that backs the vCenter syslog receiver, has a byte-identical SHA-256 hash in both ISOs. Whatever CVE-2026-59310 turns out to be, it isn’t a bug in upstream rsyslog. It has to be in VMware’s own configuration or code around it.

The rsyslog RPM hashes to the same SHA-256 in both ISOs, so whatever CVE-2026-59310 is, it lives in VMware’s configuration around rsyslog, not in the upstream daemon.
Building a pipeline instead of guessing
Since the package list wasn’t going to narrow anything down, we went one level deeper: extract every RPM on both sides into full filesystem trees, and hash-diff every file they contain.
The pipeline:
- extract both ISOs’ full RPM payload into filesystem trees,
- SHA-256 every file on both sides,
- diff the hash listings rather than the package manifest.
rsyslog itself comes back byte-identical; one suspect eliminated with certainty before any manual review starts.
Each .rpm has to be unpacked in two hops (RPM → cpio.gz → cpio → real files) before it yields a comparable filesystem tree, repeated across all 538 packages on each side. Once both trees exist, sha256sum over every file plus a diff of the two sorted hash listings replaces “63 packages changed” with a real, file-level change set: 1,061 files differ in content between 8.0.3.00900 and 8.0.3.01000.
That’s still too much to review by hand, so we triaged it with three heuristics, in priority order:
- Path relevance: anything under
*/syslog/*,*/vmdir/*,*/lwraft/*,*/afd/*, or matching*sts*, since those paths map directly to the two components named in the advisory. - Artifact type: compiled binaries and JVM bytecode ranked above plain config, since a directory-traversal-to-RCE bug and an auth-bypass bug are both far more likely to live in logic than in a comment-adjacent config value, though config wasn’t ignored, and as it turned out, one of the two fixes lived exactly there.
- Diff magnitude: a handful of changed lines or opcodes in an otherwise-identical file is a much stronger security-patch signal than a file that differs everywhere, which is usually just a recompile picking up a new embedded timestamp or build path.
That narrowed 1,061 files down to a short, reviewable list sitting inside the two component families the advisory already named.
Two threads, two write-ups
The triaged set split cleanly along the same line the advisory drew: one cluster of changes inside the identity/directory-service packages, one cluster inside the syslog templates VAMI ships for the built-in log receiver. Different protocols, different listeners, different attacker footholds; the only thing they share is a version number.
Two independent, unauthenticated paths into the same appliance: the syslog receiver on UDP/TCP 514, and the Directory Service’s vmdir/STS RPC surface. Neither requires a foothold on the other; they just happen to have been fixed in the same build.
We picked the syslog path first: it’s the smaller blast radius to isolate (a config template, not a compiled RPC surface) and it’s the one already reported as being exploited in the wild. Here’s what we found.
CVE-2026-59310: The Syslog Path Traversal (2/3)
CWE-22 (Path Traversal) → pre-authentication arbitrary file write, escalating to remote code execution under the advisory’s CVSS 9.8 rating.
Where the fix lives
vCenter’s built-in rsyslog instance (the receiver behind the appliance’s UDP/514, plaintext TCP/514 (imptcp), and TLS syslog listener) is configured from a single file:
/usr/lib/vmware-visl-integration/config/vmware-syslog.conf.template
In the vulnerable build, four dynafile templates build the on-disk log path by directly interpolating two syslog message header fields %app-name% and %hostname% with no sanitization at all:
$template defaultLoc,"/var/log/vmware/%app-name%/%app-name%-syslog.log"
$template vpxdLoc,"/var/log/vmware/%app-name%/%app-name%-syslog.log"
$template rsyslogadminLoc,"/var/log/vmware/%app-name%/%app-name%-syslog.log"
$template esxLoc,"/var/log/vmware/esx/%hostname%/%hostname%-syslog.log"
All four dynafile templates splice an attacker-controlled header field straight into a filesystem path; no allow-list, no traversal check.
Both APP-NAME and HOSTNAME are plain, attacker-controlled tokens in an RFC 5424 syslog message header; nothing authenticates or validates them before they’re spliced into a filesystem path and handed to rsyslog’s omfile dynafile writer, which auto-creates missing parent directories ($CreateDirs defaults to on) and then opens/creates the final file for append.
The full kill chain in one picture: an unauthenticated message on any of three listeners falls through to the last, unconditional ruleset rule, which routes it into esxLoc and writes it, with the attacker’s own message body, to a path they chose. That path lands inside VAMI’s static web root, so the write is readable back over HTTPS with no further steps.
No spoofed app-name required
Of the four templates, esxLoc is the simplest to reach. It’s bound by the ruleset’s last, unconditional rule:
# VC syslog server log collection
if ($hostname != $$myhostname) then ?esxLoc;esxFmt
Bound to every listener via $InputPTCPServerBindRuleset all and $InputUDPServerBindRuleset all, this is the last rule any inbound message can hit.
$InputPTCPServerBindRuleset all and $InputUDPServerBindRuleset all bind every listener (UDP, plaintext TCP, and TLS) to this same ruleset. Any inbound message whose HOSTNAME differs from the appliance’s own hostname (true of essentially anything sent from outside) falls through to esxLoc. No specific programname/app-name value is required; the attacker only has to control HOSTNAME, a plain header token present in every syslog message. The content that lands in the file comes from esxFmt:
$template esxFmt,"%timestamp:::date-rfc3339% %syslogseverity-text% %hostname% %app-name% %msg%\n"
so the attacker’s MSG body (arbitrary bytes, if they want) is written verbatim.
The vendor’s own patch confirms the data flow
The fix in 8.0.3.01000 wraps every one of these substitutions with rsyslog’s built-in secpath-replace property replacer, which strips or neutralizes .. and path-separator sequences from the value before it ever reaches the template:
EXHIBIT E The vendor’s patch, as a diff
-$template defaultLoc,"/var/log/vmware/%app-name%/%app-name%-syslog.log"
+$template defaultLoc,"/var/log/vmware/%app-name:::secpath-replace%/%app-name:::secpath-replace%-syslog.log"
-$template vpxdLoc,"/var/log/vmware/%app-name%/%app-name%-syslog.log"
+$template vpxdLoc,"/var/log/vmware/%app-name:::secpath-replace%/%app-name:::secpath-replace%-syslog.log"
-$template rsyslogadminLoc,"/var/log/vmware/%app-name%/%app-name%-syslog.log"
+$template rsyslogadminLoc,"/var/log/vmware/%app-name:::secpath-replace%/%app-name:::secpath-replace%-syslog.log"
-$template esxLoc,"/var/log/vmware/esx/%hostname%/%hostname%-syslog.log"
+$template esxLoc,"/var/log/vmware/esx/%hostname:::secpath-replace%/%hostname:::secpath-replace%-syslog.log"
The vendor’s own remediation confirms the vulnerable data flow as plainly as it gets: an unsanitized syslog header field flows straight into a filesystem path, and the whole fix is one property replacer added at each substitution point. Every one of the four templates gets the same secpath-replace wrapper; nothing else about the ruleset or the writer changed.
We isolated this by content-hash diffing the two patch ISOs’ full filesystem trees (see Part 1); rsyslog itself came back byte-identical between builds, which is what told us to keep looking in VMware’s own template rather than upstream code.
The “used twice” trick
The esxLoc template drops our attacker string %hostname% into /var/log/vmware/esx/<H>/<H>-syslog.log twice: once as a directory, once as the filename prefix. Both <H> get the identical literal value, but they sit at different filesystem depths, so a payload tuned to cancel one depth won’t cancel the other.
The trick is a quirk of the filesystem root: .. at / resolves back to /. Pad the string with more ../ than either expansion needs, and both walk all the way up to /, where the extra ../ are absorbed as no-ops. From there both push the same forward path, and only the last component differs (the second gets -syslog.log appended). One fixed value, sent once, deterministically writes an attacker-chosen file with attacker-controlled content.
Different starting depths, same surplus trick: 16 leading ../ segments overshoot both the 4-deep and the 6-deep starting point, so both expansions bottom out at root and push the identical forward path back down. They diverge only at the very last component.
Concretely, with H = "../"*16 + "opt/vmware/share/htdocs/configurev2/MOBETA":
- Directory
<H>(start depth 4): 4 of the 16../reach/, the remaining 12 are no-ops, thenopt/vmware/share/htdocs/configurev2/MOBETAis pushed →/opt/vmware/share/htdocs/configurev2/MOBETA, created as a directory by rsyslog’s mkdir-parents step. - File
<H>-syslog.log(start depth 6, inside the directory just created): 6 of the 16../reach/, the rest are no-ops, the same forward path is pushed, and the final componentMOBETA-syslog.loglands it at/opt/vmware/share/htdocs/configurev2/MOBETA-syslog.log.
Impact
/opt/vmware/share/htdocs/ is the document root VAMI (the appliance-management web UI on :5480) serves statically, and configurev2/ is a path under it reachable without further traversal from the HTTP client side. A file planted there via the syslog primitive above is immediately retrievable over HTTPS:
https://<vcenter>:5480/configurev2/MOBETA-syslog.log


Confirmed end-to-end. The checker sends a crafted RFC 5424 syslog message with the path-traversal HOSTNAME value, then fetches the planted file from VAMI’s HTTPS interface. Marker found in the retrieved content confirms unauthenticated arbitrary write and pre-authentication read-back on the target appliance.
At minimum, that’s an unauthenticated, pre-auth arbitrary file write with attacker-controlled content, readable back over HTTPS, a primitive we confirmed end-to-end. Escalation to full remote code execution, consistent with the advisory’s CVSS 9.8 rating and “arbitrary code execution” language, requires steering the same primitive at an executable sink instead of a static htdocs path; a CGI-executable directory or a cron drop-in are the obvious candidates. We successfully achieved remote code execution through this path; we leave the exercise of escalating this arbitrary file write to RCE to the reader and do not publish the full exploitation chain here.
Preconditions
- Network reachability to the vCenter appliance’s syslog listener: UDP/514, plaintext TCP/514 (
imptcp), or the TLS syslog port (imtcp); none of them require authentication. - No
programname/app-namespoofing needed on theesxLocpath; onlyHOSTNAME != vCenter’s own hostname, which is trivially true for any external sender. - The target directory tree must be writable by the rsyslog process’s user and exist up through
configurev2/; it does, shipped by VAMI itself.
Detection & mitigation
- Patch to 8.0.3.01000 or later: it adds
secpath-replacesanitization to%app-name%/%hostname%on all four templates. - Network-layer: restrict inbound UDP/514, TCP/514, and TCP/6514 to trusted syslog forwarders only.
- Detection: alert on any file appearing under
/var/log/vmware/**or/opt/vmware/share/htdocs/**whose path contains..-derived components, or on syslogHOSTNAME/APP-NAMEheader values containing/or..; both are invalid per RFC 5424 §6.2.4 and are a strong indicator of this technique.
Detection rules
Two Sigma rules (Sekoia.IO-compatible YAML) cover the network and host dimensions; a YARA rule adds packet-level coverage for Suricata, Zeek, or inline NTA sensors.
title: CVE-2026-59310: vCenter Syslog HOSTNAME Path Traversal Attempt
id: 7f4e2a1c-83b0-4d9e-a5c7-f612e8903421
status: experimental
description: |
Detects syslog messages sent to vCenter's rsyslog receiver (TCP/UDP 514,
TLS 6514) whose HOSTNAME header field contains path-traversal sequences.
In vCenter 8.0.3.00900 and earlier the HOSTNAME field is interpolated
unsanitized into the esxLoc dynafile path template, enabling unauthenticated
arbitrary file write (CVE-2026-59310, CVSS 9.8).
A HOSTNAME value containing ../ is invalid per RFC 5424 s6.2.4 and is not
produced by any well-behaved syslog sender.
references:
- https://www.vmware.com/security/advisories/VMSA-2026-0006.html
- https://www.rfc-editor.org/rfc/rfc5424
author: Raphael Dray, Mobeta
date: 2026/08/21
tags:
- attack.initial_access
- attack.t1190
- attack.persistence
- attack.t1505.003
logsource:
category: network_traffic
product: syslog
detection:
selection_port:
dst_port:
- 514
- 6514
selection_payload:
payload|contains:
- '../'
- '%2e%2e%2f'
- '%2e%2e/'
condition: selection_port and selection_payload
falsepositives:
- None; a HOSTNAME containing ../ is RFC-invalid and has no legitimate origin
from a correctly implemented syslog sender
level: high
Sigma · network traffic: fires on any syslog datagram to ports 514/6514 whose payload carries a traversal sequence, in any encoding. Deploy on a sensor with RFC 5424 visibility or on the receiver itself. A ../ in a syslog HOSTNAME is structurally invalid, so benign matches are near-impossible.
title: CVE-2026-59310: rsyslog File Creation in vCenter VAMI Web Root
id: 3c8a5f2e-94d1-4b7f-b3e6-a017c5284b63
status: experimental
description: |
Detects file-creation events produced by the rsyslogd process inside
/opt/vmware/share/htdocs/, the document root served by vCenter's VAMI
management interface on TCP 5480. rsyslogd has no legitimate reason to
write to this directory; any file it creates there is a high-confidence
indicator of successful CVE-2026-59310 exploitation.
references:
- https://www.vmware.com/security/advisories/VMSA-2026-0006.html
author: Raphael Dray, Mobeta
date: 2026/08/21
tags:
- attack.initial_access
- attack.t1190
- attack.persistence
- attack.t1505.003
logsource:
category: file_event
product: linux
detection:
selection:
Image|endswith: '/rsyslogd'
TargetFilename|startswith:
- '/opt/vmware/share/htdocs/'
condition: selection
falsepositives:
- None; rsyslogd has no legitimate write access to the VAMI web root
level: critical
Sigma · file event: host-level coverage for auditd, Sysmon for Linux, or Falco. It fires on successful exploitation rather than the attempt, making it a post-compromise indicator, and the /rsyslogd Image filter rules out other web-root writers.
rule CVE_2026_59310_vCenter_Syslog_PathTraversal
{
meta:
description = "Detects RFC 5424 syslog messages with path-traversal sequences"
" in the HOSTNAME field targeting vCenter's rsyslog dynafile"
" template (CVE-2026-59310, CVSS 9.8)"
author = "Raphael Dray, Mobeta"
date = "2026-08-21"
reference = "https://www.vmware.com/security/advisories/VMSA-2026-0006.html"
cve = "CVE-2026-59310"
strings:
// RFC 5424 PRI + VERSION 1 prefix
$rfc5424_hdr = /^<[0-9]{1,3}>1 /
// Traversal sequences in the HOSTNAME field (bare and percent-encoded)
$trav_bare = "../../" ascii
$trav_pct_lo = "%2e%2e%2f" ascii
$trav_pct_up = "%2E%2E%2F" ascii
// Target path components for the vCenter VAMI htdocs write primitive
$path_htdocs = "opt/vmware/share/htdocs" ascii
$path_conf2 = "configurev2" ascii
condition:
$rfc5424_hdr and
( $trav_bare or $trav_pct_lo or $trav_pct_up ) and
( $path_htdocs or $path_conf2 )
}
YARA: packet-level detection for live captures or offline pcap (yara -r). The three clauses demand the RFC 5424 PRI and version prefix, an encoded traversal, and a target-path fragment together, so isolated coincidences never trigger it.
That’s the syslog side fully rooted, from advisory to working write primitive. The identity-service side took the same pipeline somewhere different.
CVE-2026-59309: The SRP Auth Bypass (3/3)
Authentication bypass in VMware Directory Service, CVSS 9.8 (per VMSA-2026-0006).
Running the same pipeline on the identity cluster
Part 1’s triage split the 1,061 changed files into two clusters along the lines the advisory drew. The syslog cluster resolved to a single template file: four lines, one property replacer, done. The identity cluster looked, at first, like it had resolved to something narrower still: one changed binary out of five swept. It hadn’t. Chasing that one binary down with a proper section-level diff of the compiled code is what actually cracked this bug open, by ruling the first suspect out completely.
We started from every compiled artifact plausibly in vmdird’s authentication path (the daemon itself, its LDAP-auth library, its client library, its SASL bind handler, and the Likewise LSASS auth-provider plugin vmdird uses for local authentication) and hashed each one on both sides:
| vmdird (daemon) | usr/lib/vmware-vmdir/sbin/vmdird: byte-identical SHA-256 |
| libvmdirauth.so | usr/lib/vmware-vmdir/lib64/libvmdirauth.so: byte-identical SHA-256 |
| libvmdirclient.so | usr/lib/vmware-vmdir/lib64/libvmdirclient.so: byte-identical SHA-256 |
| libsaslvmdirdb.so | usr/lib/vmware-vmdir/lib64/libsaslvmdirdb.so: byte-identical SHA-256 |
| liblsass_auth_provider_vmdir.so | opt/likewise/lib64/liblsass_auth_provider_vmdir.so: SHA-256 differs |
vmdird itself (the actual LDAP-speaking directory daemon) never moved. Neither did its own auth or client libraries. The one file that flagged lives one layer down, in the OS-level authentication glue vCenter inherited along with the rest of the Likewise Open codebase. It’s not stripped, either: both builds expose the same three entry points by name: VmDirCheckUserInList, VmDirAuthenticateUserPam, VmDirAuthenticateUserEx, same globals, same symbol count, nothing added or removed from the interface. On a first pass, that’s a promising lead: a changed file, unchanged interface, sitting right in vmdird’s local-auth glue.
It’s also wrong, and a closer look at the compiled sections proves it in about thirty seconds.
The flag that wasn’t a fix
A whole-file SHA-256 only tells you two files differ; it never tells you where. An ELF’s section table lists every section with its file offset and size, so diffing each section’s raw bytes on its own, rather than the file as a whole, turns a vague “changed” into something you can actually pin down:
liblsass_auth_provider_vmdir.so’s .text, .rodata, .data, .symtab, and .dynsym are all byte-identical between builds. Only the DWARF debug sections differ, and that difference is entirely the compiler’s embedded build path, bora-25333653 → bora-25599006. A function-by-function similarity pass confirms it independently: all 143 functions match at effectively 100% similarity. Not one instruction changed.
The symbol table backs this up too: VmDirCheckUserInList, VmDirAuthenticateUserPam, and VmDirAuthenticateUserEx sit at the exact same addresses with the exact same sizes in both builds. The file’s SHA-256 differs because 15,479 bytes of .debug_info/.debug_line/.debug_str differ: DWARF metadata that embeds the compiler’s absolute build directory, which shifted with the version bump the same way it did for every other VMware-owned artifact in this release train (Part 1, Exhibit A). Rebuild the same unchanged source under a new build number and the debug info changes even though the machine code doesn’t. liblsass_auth_provider_vmdir.so is a full false positive: flagged by hash, cleared by section, cleared again by function-level comparison. The fix isn’t here.
Widening the search
That meant re-running the sweep properly: not five hand-picked binaries, but every ELF in the entire 1,061-file changed set, hashing each one’s .text section individually instead of trusting the whole-file hash.
Only 28 of the 1,061 flagged files carry a real .text difference; almost all of them (vpxd, vSAN’s libraries, Envoy’s log writers) are unrelated components differing by a handful of relocation-noise bytes. Exactly one sits inside the identity/auth stack: opt/likewise/lib64/sasl2/libsrp.so.3.0.0, the Cyrus SASL SRP mechanism plugin. Its .text grows by 208 bytes, one function resizes, and a brand-new PLT import BN_div appears, one 8.0.3.00900 never linked at all.
That last part is the giveaway before a single instruction gets read. The import table on both builds shows BN_div linked only in 8.0.3.01000: a new call to OpenSSL’s big-number division routine, in a SASL plugin whose entire job is modular-exponentiation arithmetic.

Diffing the two import tables says it in one line: BN_div, OpenSSL’s big-number division routine, is a brand-new dependency in the patched libsrp.so, absent from 8.0.3.00900.
The symbol table narrows it further: every function in the file keeps its exact byte size across both builds except one: srp_server_mech_step2.isra.15, which grows from 0x71c to 0x7e9 bytes. A function-similarity pass puts a number on it: every function in the 193-symbol binary matches at effectively 100% similarity except this one, which lands at 0.75, by a wide margin the only real code change anywhere in the file.

nm -S puts it beyond doubt: srp_server_mech_step2.isra.15 is the only symbol whose size changed, 0x71c → 0x7e9, and joining the two symbol tables on name confirms nothing else in the file moved.
libsrp.so.3.0.0 is Cyrus SASL’s implementation of SRP (Secure Remote Password), a zero-knowledge password-authenticated key exchange, and the SASL mechanism vmdird uses for LDAP bind. The string cmusaslsecretSRP is still in the binary’s .rodata, the standard attribute name the SRP mechanism reads a user’s password verifier from; this plugin is live, not vendored-but-unused. libsaslvmdirdb.so, the auxprop plugin that supplies those verifiers out of vmdir’s own data store, is byte-identical between builds (see the table above); the bug isn’t in how secrets are stored or fetched. It’s in the protocol arithmetic itself.
The missing check
Decompiling srp_server_mech_step2 in both builds and diffing the two results shows exactly one inserted block, sitting right after an existing check that survives unchanged in both versions:

Ghidra decompiler, vulnerable build on the left, patched on the right. The entire fix is the green block on the right: BN_div computes A mod N, BN_is_zero tests the remainder, and the bind bails out with the new Illegal value for 'A' (A mod N == 0) message before A is ever used to derive a key.
Both builds reject a client public value A ≤ 0. Only 8.0.3.01000 goes further: it computes A mod N via the new BN_div call and rejects the bind if the remainder is zero:
BN_new/BN_CTX_newfor the scratch stateBN_is_zeroon the remainder- bailing out with a brand-new error string,
“Illegal value for 'A' (A mod N == 0)”, beforeAis ever used to derive a session key
That error string is the single cleanest piece of evidence in this whole investigation: it does not exist anywhere in 8.0.3.00900’s string table. A pass over both binaries’ .rodata confirms it: the vulnerable build has a short “Illegal value for 'A'” (guarding the A > 0 check both versions share) and, separately, “Illegal value for 'B'” on the client side, but never the A mod N == 0 variant, and never a BN_div call anywhere in the file. Both the message and the check it belongs to are wholly new in the patch.

rabin2 -z over both builds, in loop order vulnerable then patched: the vulnerable build carries only the three pre-existing Illegal value strings, while the patched build gains a fourth, Illegal value for 'A' (A mod N == 0), the message that guards the new check.
The check being added is A mod N == 0, precisely the safeguard RFC 5054 §3.1 mandates and precisely the one that was missing: “the host MUST abort the authentication attempt if A % N is zero.” Without it, an unauthenticated attacker who knows nothing but a valid identity string (administrator@vsphere.local, for instance) can open an SRP SASL bind against vmdird and send A = N as their public ephemeral value. (Plain A = 0 is the one degenerate value the pre-existing A > 0 guard already rejects; what it misses is every nonzero multiple of N.)
The cryptographic detail (math-heavy). Collapse this if you want to skip the formulas.
The forced collapse of the shared secret
In the SRP protocol, the server derives the shared secret as:
where is the account’s stored password verifier and is a deterministic value computed from both public ephemerals. With , the product reduces to zero before exponentiation, so the shared secret collapses unconditionally to , regardless of . The session key is then derived as the SHA-1 hash of the big-endian encoding of . But BN_bn2bin(0) produces an empty byte string, so:
SHA-1 of the empty string is a public constant. An attacker does not need to know the account’s password, its verifier, or the factorisation of to arrive at this key.
Forging client evidence
SRP requires the client to prove knowledge of the password by sending alongside . The Cyrus SASL SRP implementation computes:
where is SHA-1, is the identity string, is the server-supplied salt, is the group generator, is the server’s public ephemeral, and encodes the offered security-layer options. Every input is either a known constant, a value the attacker chose (), or a value the server sent openly in step 1 (, , , ). With in hand, the attacker computes exactly and sends it in step 2. The server verifies it, finds it correct, and replies with its own evidence value together with the session IVs needed to install the post-bind security layer.
The 3-step bind and mandatory security layers
A Cyrus SASL SRP bind takes three round trips:
- Step 1: the client sends an identity, and the server responds with , , , and .
- Step 2: the client sends and .
- Step 3: the server replies with , and the bind completes.
What the advisory does not surface is that the Cyrus SASL SRP mechanism, as deployed in vmdird, enforces a mandatory security layer: immediately after the step-3 acknowledgement, vmdird installs an AES-OFB and HMAC-SHA1 cipher over the LDAP socket, and any later LDAP PDU sent in plaintext is rejected outright. Full exploitation therefore means operating that post-bind security layer too, under the forged session key.
The attacker rides this exact exchange. Sending A = N collapses the shared key to a public constant, so once the bind completes, every security-layer key is known as well.
Post-bind: the SASL security layer is also broken
Ghidra analysis of libsrp.so.3.0.0 reveals the security layer parameters directly. The cipher is aes-128-ofb (entry 3 of the cipher_options table at offset 0x0030c3c0); OFB mode requires no padding. The MAC is HMAC-SHA1, applied in Encrypt-then-MAC order: srp_encode at 0x00103810 runs the OFB encryption pass first, then appends the HMAC-SHA1 tag over the resulting ciphertext. On the receive side, _plug_decode at 0x00108280 reads a 4-byte big-endian inner-length field directly from the socket (no outer sockbuf length wrapper) and passes the frame to srp_decode_packet for MAC verification and decryption. Since the attacker controls , all derived key material is known:
- AES-128 key: (first 16 bytes of )
- HMAC-SHA1 key: (full 20 bytes)
- Encryption IV (
enc_iv): the server-generatedsIV, received in the step-2 response - Decryption IV (
dec_iv): the client-chosencIV, sent as 16 zero bytes in step 2
An attacker with a valid identity string and TCP access to vmdird’s LDAP port can execute the full chain: forge the 3-step SRP bind with , derive , compute all security-layer keys from it, and issue arbitrary LDAP requests as the authenticated identity. We confirmed this end-to-end: a subtree search under dc=vsphere,dc=local returns full directory entries including the membership of cn=Administrators, listing Administrator, all domain-joined machine accounts, and every vCenter solution-user service principal. That is unauthenticated read access to the complete vSphere SSO directory, in addition to whatever LDAP write privileges the target identity carries, with no password required.

Confirmed end-to-end. The checker completes the full 3-step SRP SASL bind as administrator@vsphere.local with no password: sends A = N, forges from , and uses the established AES-OFB security-layer session to query cn=Administrators,cn=Builtin,dc=vsphere,dc=local. The LDAP response lists 9 members, including domain machine accounts and every solution-user service principal.
Detection
Because CVE-2026-59309 turns one crafted bignum inside a valid LDAP message into a bypass, the rules below cover its three observable phases:
- the connection attempt
- the post-bind enumeration
- the packet signature of the degenerate A value
title: CVE-2026-59309: LDAP Connection to vCenter Directory Service from Unexpected Source
id: 9e1b3d7a-c452-4f8e-8b2d-70e3a914c582
status: experimental
description: |
Detects TCP connections to vCenter Directory Service (vmdird) LDAP ports
(389, 636, 2020) from hosts outside the designated management network.
CVE-2026-59309 requires only a TCP connection and a known account identity;
no credential is needed. A bind attempt from a non-management source is
a prerequisite for exploitation and warrants investigation.
Tune filter_mgmt_cidr to match your management-plane CIDR.
references:
- https://www.vmware.com/security/advisories/VMSA-2026-0006.html
- https://www.rfc-editor.org/rfc/rfc5054
author: Raphael Dray, Mobeta
date: 2026/08/21
tags:
- attack.initial_access
- attack.t1190
- attack.credential_access
- attack.t1078.001
logsource:
category: network_connection
product: linux
detection:
selection:
Initiated: 'true'
DestinationPort:
- 389
- 636
- 2020
filter_mgmt_cidr:
SourceIp|cidr:
- '10.0.0.0/8' # adjust to your management-plane CIDR
- '172.16.0.0/12'
- '192.168.0.0/16'
condition: selection and not filter_mgmt_cidr
falsepositives:
- Legitimate management tooling reaching vmdird from expected subnets;
tune filter_mgmt_cidr before deployment
level: medium
Sigma · network connection: the earliest signal, firing on any connection from outside the management CIDR to vmdird’s LDAP ports before authentication completes. Scope filter_mgmt_cidr tightly to your legitimate management hosts to keep it quiet.
title: CVE-2026-59309: Successful SASL SRP Bind Followed by Sensitive LDAP Search
id: 2f7c4a9e-d360-4b1c-a8f5-c93b1e075d4a
status: experimental
description: |
Detects a behavioral pattern consistent with CVE-2026-59309 exploitation:
a successful SASL SRP bind to vmdird immediately followed by LDAP search
operations targeting sensitive Directory Information Tree subtrees.
An attacker exploiting this bug authenticates without knowing the account
password; the first observable post-bind action is typically enumeration
of the vSphere SSO directory (cn=Users, cn=Administrators,
cn=ServicePrincipals). Correlate events within a 5-second window per
source IP. Requires vmdird access logging at DEBUG or VERBOSE level.
references:
- https://www.vmware.com/security/advisories/VMSA-2026-0006.html
author: Raphael Dray, Mobeta
date: 2026/08/21
tags:
- attack.initial_access
- attack.t1190
- attack.discovery
- attack.t1087.002
logsource:
product: vmware
service: vmdir
detection:
bind_success:
EventType: 'BIND'
AuthMechanism: 'SASL/SRP'
Result: 'success'
sensitive_search:
EventType: 'SEARCH'
BaseDN|contains:
- 'dc=vsphere,dc=local'
- 'cn=Users'
- 'cn=Administrators'
- 'cn=ServicePrincipals'
condition: bind_success and sensitive_search
falsepositives:
- Legitimate administrative tooling performing SASL SRP binds followed by
directory lookups; validate against known management hosts and service accounts
level: high
Sigma · vmdird log: a behavioural rule needing bind and search logging (log_level = VERBOSE in vmdir.cfg). A successful SRP bind followed immediately by a search on a sensitive base DN is the exploit’s signature; service accounts bind once at startup and never enumerate cn=Administrators on the fly.
rule CVE_2026_59309_vCenter_SRP_DegeneratePublicValue
{
meta:
description = "Detects LDAP packets containing a Cyrus SASL SRP step-2 bind"
" where the client public value A equals the RFC 5054 group"
" modulus N, the degenerate value that collapses the shared"
" secret to zero (CVE-2026-59309, CVSS 9.8)"
author = "Raphael Dray, Mobeta"
date = "2026-08-21"
reference = "https://www.vmware.com/security/advisories/VMSA-2026-0006.html"
cve = "CVE-2026-59309"
strings:
// LDAP BindRequest (tag 0x60) with SASL credentials for mechanism "SRP"
// 60=BindRequest 02 01 03=version 3 04 00=empty DN
// a3=SaslCredentials 04 03 53 52 50="SRP"
$ldap_srp_bind = { 60 ?? 02 01 03 04 00 a3 ?? 04 03 53 52 50 }
// First 32 bytes of RFC 5054 1024-bit group prime N
// Sending A = N means A starts with exactly these bytes
$N_1024 = {
EE AF 0A B9 AD B3 8D D6 9C 33 F8 0A FA 8F C5 E8
60 72 61 87 75 FF 3C 0B 9E A2 31 4C 9C 25 65 76
}
// First 32 bytes of RFC 5054 2048-bit group prime N
$N_2048 = {
AC 6B DB 41 32 4A 9A 9B F6 06 E8 C3 97 3B E7 36
29 72 02 24 8B 74 7D 8A 82 35 EF B6 17 F9 C0 AE
}
condition:
$ldap_srp_bind and ( $N_1024 or $N_2048 )
}
YARA: catches A = N by its bytes. The first 32 bytes of the RFC 5054 1024- and 2048-bit primes are fixed public constants that appear verbatim at the head of the A field only in the degenerate case, and $ldap_srp_bind anchors the match to a real LDAP SRP BindRequest. Apply on a 389/636/2020 mirror or to vmdird pcap.
Mitigation
- Patch to 8.0.3.01000 or later: it adds the
A mod N == 0rejection (and, per the client-side symmetry visible in the same binary, the equivalent check belongs on any custom SRP client speaking to vmdird too). - Network-layer: vmdird’s LDAP ports (389, 636, 2020) should be reachable only from management networks; this bug requires nothing but a TCP connection and a known account identity, no prior credential. Whether that segmentation actually holds is what an internal network penetration test measures.
- Detection: log and alert on SRP SASL bind attempts where the client’s public value
Ais0or a multiple of the advertisedN; that’s not a naturally occurring value from a real SRP client and is a strong, specific indicator of this exact technique, well ahead of any generic “anomalous auth success” heuristic.
Closing the series
Three things came out of one build number:
- a public advisory that told us that two critical bugs existed
- a diffing pipeline that told us where (down to a single config template for CVE-2026-59310 and a single function, in a single SASL plugin, for CVE-2026-59309)
- and, for both bugs, a root cause with a data flow an attacker could actually walk
The syslog bug came from following an unsanitized field into a path. The auth bypass came from not trusting the file the initial hash diff pointed at, verifying that lead to destruction at the section level, and widening the net until the one real code change in the entire identity stack turned up in a place a package-name grep would never have suggested: a bundled SASL mechanism plugin, one missing bignum comparison away from a full authentication bypass.
The same patch-diffing and source-level analysis backs our white-box pentest engagements with source access.
Resources
Advisory and standards
- VMSA-2026-0006: the VMware advisory covering CVE-2026-59309 and CVE-2026-59310.
- RFC 5424: the syslog protocol. Section 6.2.4 defines the
HOSTNAME/APP-NAMEfields abused in the traversal. - RFC 5054: SRP for TLS. Section 3.1 is the
A % N == 0safeguard that was missing. - CWE-22: path traversal, the class behind CVE-2026-59310.
Components and tooling
- rsyslog property replacer: the
secpath-replacereplacer the patch adds, and thedynafilemechanics it protects. - Cyrus SASL: the SASL library shipping the SRP mechanism plugin (
libsrp.so) that carried CVE-2026-59309. - Sigma and YARA: the rule formats used for the detection content above.
Related Mobeta work
- Internal network penetration testing: whether segmentation around ports like vmdird’s 389/636/2020 actually holds.
- White-box pentest with source access: the same patch-diffing and source-level analysis, applied to your own code.
Ces techniques s’appliquent
à votre périmètre ?
Un échange de 30 min avec nos pentesters certifiés pour cadrer votre exposition. Sans engagement.