# Root Cause Analysis: CVE-2026-73570

## Summary

CVE-2026-73570 is an unauthenticated command-injection vulnerability in Zimbra Collaboration's SNMP monitoring path. An external SMTP peer can make the ZCS-packaged Postfix service log attacker-controlled text resembling a Zimbra `Service status change` record. The installed, packaged `zmswatch` process matches that log line and passes its attacker-controlled `SERVICE` capture to `dosnmp()`. Before ZCS 10.1.20, `dosnmp()` interpolates `SERVICE` into a Perl backtick command, invoking `/bin/sh` and permitting command execution as the `zimbra` operating-system user.

## Impact

- **Affected component:** Zimbra Collaboration Suite MTA/SNMP monitoring (`zimbra-mta`, optional `zimbra-snmp`, `/opt/zimbra/conf/swatchrc`, packaged Postfix, and packaged `zmswatch`).
- **Affected versions:** ZCS releases before 10.1.20 with the vulnerable Swatchdog configuration.
- **Required configuration:** The SNMP package is installed, SNMP notifications are enabled, `zmswatch` is running, and the SMTP listener is reachable.
- **Risk:** High. A remote unauthenticated attacker can execute shell commands as the `zimbra` account, which can disclose Zimbra configuration/credentials or alter mail-service data.

## Impact Parity

- **Disclosed/claimed maximum impact:** Unauthenticated remote code/command execution through the public SMTP protocol path.
- **Reproduced impact from this run:** Two clean installed-ZCS product instances accepted attacker-controlled SMTP bytes over an external TCP connection to packaged Postfix, logged them to `/var/log/zimbra.log`, processed them with packaged `zmswatch` running as `zimbra`, and created unique shell-expanded markers: `v1:u999:nzimbra` and `v2:u999:nzimbra`.
- **Parity:** `full`
- **Not demonstrated:** No interactive shell, persistence, credential theft, or privilege escalation beyond the `zimbra` account was attempted or claimed.

## Root Cause

The vulnerable Zimbra Swatchdog rules match Postfix log records of the form `: Service status change: (\S+) (.*) changed from ...`. A compact SMTP command-pipelining violation can place attacker input in that log format. The second capture becomes `SERVICE` and flows to `dosnmp()`.

The vulnerable ZCS 10.1.0 package input contains:

```perl
`$snmptrap $snmpsvctrap $snmpsvcname s $args{SERVICE} $snmpsvcstatus i $statuses{$args{STATUS}}`;
```

Perl backticks invoke a shell. Consequently, shell metacharacters and command substitutions in `SERVICE` are interpreted instead of remaining one SNMP argument. The installed vulnerable `/opt/zimbra/conf/swatchrc.in` has SHA-256 `b0b36f69787aad1ad02b3bccac8043187de7113bea1aa49669e31ed98160c02e`.

The authentic ZCS 10.1.20 `zimbra-mta-patch` package replaces this with LIST-form `system()`:

```perl
system("/opt/zimbra/common/bin/snmptrap", "-v", "2c", "-c", "zimbra", $traphost, "", $snmpsvctrap, $snmpsvcname, "s", $args{SERVICE}, $snmpsvcstatus, "i", $statuses{$args{STATUS}});
```

LIST-form execution passes `SERVICE` as one literal argument without invoking a shell. The authentic fixed `swatchrc.in` has SHA-256 `06e9be7dfad44519dd3f0f9c70673d7ccce6dbfb18bf54dc5ff2c5078a4c4a5c`. The exact official fixed package is `zimbra-mta-patch 10.1.20.1783342495-1.u22`, package SHA-256 `f242ee41af609b940c091d4bf9dab7ff1dd641134865d045850cfe00cbdee593`.

The release-level fix is documented at <https://wiki.zimbra.com/wiki/Zimbra_Releases/10.1.20>; this reproduction does not rely on a generated approximation or an inferred source commit.

## Reproduction Steps

1. Run `bash bundle/repro/reproduction_steps.sh` from any directory. The script uses `PRUVA_ROOT` when set.
2. The script reads `bundle/project_cache_context.json` and first uses the prepared project cache. If necessary, it downloads the pinned official ZCS 10.1.0 Ubuntu 22 installer archive and authentic ZCS 10.1.20 MTA patch, verifies their SHA-256 digests, and constructs installed product images.
3. It creates two clean vulnerable and two clean fixed instances. Every instance starts installed ZCS LDAP state, packaged ZCS Postfix, rsyslog's Zimbra mail-log route, and packaged `zmswatch` as `zimbra`.
4. For each instance, an external Python TCP peer connects through a host-loopback published port, receives the real Postfix banner, and pipelines `VRFY` with a 99-byte forged service-status record.
5. Vulnerable success requires both unique victim-shell markers. Fixed success requires the authentic 10.1.20 package identity, the same log/parser path, no marker, and a trace showing the entire metacharacter-bearing payload preserved as literal `snmptrap` `argv[9]`.
6. The final script was executed successfully twice consecutively; each execution internally performed all four isolated attempts.

## Evidence

Primary current-run evidence is under `bundle/repro/evidence/`:

- `source-identity.txt` binds the official archive/package digests and immutable vulnerable/fixed runtime image IDs.
- `vulnerable-1/attack-request.txt` contains the actual SMTP transaction bytes.
- `vulnerable-1/smtp.transcript` records the real ZCS Postfix banner and protocol exchange.
- `vulnerable-1/zimbra.log` proves packaged Postfix logged the attacker-selected forged record.
- `vulnerable-1/processes.txt` shows Postfix `smtpd` as `postfix` and packaged Swatchdog plus its generated parser as `zimbra`.
- `vulnerable-1/package-identity.txt` records ZCS 10.1.0, `zimbra-postfix 3.6.14`, `zimbra-perl-swatchdog 3.2.4`, and `zimbra-snmp 10.1.0`.
- `vulnerable-1/marker.txt` contains `v1:u999:nzimbra` (SHA-256 `85fe5f908055df71908e93700df6808558cda5b1e30d390f0247ec365f4d2803`).
- `vulnerable-2/marker.txt` contains `v2:u999:nzimbra` (SHA-256 `483742d1e7d96aab6b5fdab8ac45189bed7036ccc9ba3d1b0cf150dbb54bf7ba`).
- `fixed-1/result.json` and `fixed-2/result.json` record `target_path_reached=true` and `marker_present=false`.
- `fixed-1/snmptrap.trace` and `fixed-2/snmptrap.trace` show the attack string preserved literally as one argument under LIST-form `system()`.
- `bundle/repro/runtime_manifest.json` records `entrypoint_kind=tcp_peer`, all three runtime gates as true, exact target identities, and digest-bound proof artifacts.
- `bundle/repro/validation_verdict.json` records `confirmed`, `network_protocol`, `production_path`, and observed `code_execution`.
- `bundle/logs/reproduction_steps.log` records the latest complete orchestration result.

Key current-run excerpts:

```text
v1:u999:nzimbra
v2:u999:nzimbra
```

```text
postfix smtpd ... improper command pipelining after VRFY ... : Service status change: h x;echo v1:u$(id -u):n$(id -un)>/tmp/p0;# ...
```

```text
argv[9]=<x;echo f1:u$(id -u):n$(id -un)>/tmp/p2;#>
```

## Recommendations / Next Steps

- Upgrade affected installations to ZCS 10.1.20 or later with the complete vendor-supported package closure.
- Verify that `/opt/zimbra/conf/swatchrc.in` uses LIST-form `system()` and regenerate `/opt/zimbra/conf/swatchrc` with the normal Zimbra tooling.
- Until upgrading, disable SNMP notifications or stop `zmswatch` if operationally acceptable.
- Inspect Zimbra logs and filesystem/web-root modifications for exploitation indicators; rotate credentials in Zimbra configuration if compromise is suspected.
- Add an end-to-end regression that sends shell metacharacters through SMTP and asserts they arrive at `snmptrap` as one literal argument without command side effects.

## Additional Notes

- The reproduction is idempotent. It deletes prior attempt evidence, starts fresh isolated containers and ports, and removes every attempt container on exit.
- The primary oracle is non-sanitized product-visible shell execution through the installed production path; no sanitizer was used.
- The real `snmptrap` executable is used for the vulnerable success path. A recording wrapper is introduced only after the fixed negative-control path has already proven no marker, solely to demonstrate literal fixed-version argv boundaries.
- LDAP's test database is recreated per instance because installation-time LMDB files are sparse and unsuitable for a portable image; this does not replace or modify Postfix, `zmswatch`, `swatchrc`, or the vulnerable/fixed sink.
- The trigger must remain within Postfix's observed 100-byte client-text log limit; the final forged record is 99 bytes.
