zimbra-snmp-rce
Сигурност · Zimbra · Активно експлоатираноSecurity · Zimbra · Actively exploited

Критична уязвимост в Zimbra:
командна инжекция през SNMP мониторинга
Critical Zimbra vulnerability:
command injection via the SNMP monitor

Неоторизирано отдалечено изпълнение на код през порт 25. Как работи, как да проверите дали сте засегнати, и как да се защитите — докато официалният пач стигне до вас.Unauthenticated remote code execution via port 25. How it works, how to check if you are affected, and how to protect yourself — until the official patch reaches you.

Оповестена от Zimbra: 26 юни 2026 · Поправена в: Zimbra 10.1.20 · CVE: все още не е присвоен
Написано за ИТ общността · последна редакция: 6 август 2026
Disclosed by Zimbra: 26 Jun 2026 · Fixed in: Zimbra 10.1.20 · CVE: not yet assigned
Written for the sysadmin community · last updated: 6 Aug 2026

⚠ Накратко — направете това сега⚠ TL;DR — do this now

Ако управлявате Zimbra под версия 10.1.20 (включително целия EOL клон 8.8.15), вероятно сте уязвими. Експлоатира се неоторизирано, само чрез изпращане на имейл. Ако още не можете да обновите:If you run Zimbra below version 10.1.20 (including the entire EOL 8.8.15 branch), you are likely vulnerable. It is exploited unauthenticated, just by sending an email. If you cannot upgrade yet:

su - zimbra -c "zmlocalconfig -e snmp_notify=0"
su - zimbra -c "zmswatchctl restart"

Това затваря конкретния път за изпълнение. Пълните проверки, обяснението и допълнителната защита са по-долу.This closes the specific execution path. Full checks, explanation and additional defense are below.

СъдържаниеContents
  1. Какво представляваWhat it is
  2. Как работи атакатаHow the attack works
  3. Кой е засегнатWho is affected
  4. Проверка дали сте уязвимиCheck if you are vulnerable
  5. МитигацияMitigation
  6. ВерификацияVerification
  7. Локална защита: guard + IDSLocal defense: guard + IDS
  8. Индикатори за компрометиранеIndicators of compromise
  9. Траен фикс и ембарготоPermanent fix & the embargo
  10. Защо тази страницаWhy this page

01Какво представляваWhat it is

Критична командна инжекция в SNMP мониторинг компонента на Zimbra. Активира се, когато SNMP известията са включени и услугата zmswatch (Swatchdog) работи — а това е конфигурацията по подразбиране.A critical command injection in Zimbra's SNMP monitoring component. It triggers when SNMP notifications are enabled and the zmswatch (Swatchdog) service is running — which is the default configuration.

Неоторизиран отдалечен атакуващ може да изпълни произволни команди с правата на потребител zimbra, само като изпрати специално подготвен имейл към SMTP порт 25. Не е нужен валиден акаунт, парола или взаимодействие от потребител.An unauthenticated remote attacker can run arbitrary commands as the zimbra user, simply by sending a crafted email to SMTP port 25. No valid account, password or user interaction is required.

Zimbra оповести уязвимостта на 26 юни 2026 г. и я поправи в 10.1.20 (средата на юли 2026 г.). По механизъм е сравнима с CVE-2024-45519 (postjournal), но е отделен дефект в друг компонент. Сървър, закърпен срещу CVE-2024-45519, все още е уязвим на този проблем.Zimbra disclosed it on 26 Jun 2026 and fixed it in 10.1.20 (mid-July 2026). By mechanism it resembles CVE-2024-45519 (postjournal), but it is a separate defect in a different component. A server patched against CVE-2024-45519 is still vulnerable to this one.

02Как работи атакатаHow the attack works

Механизмът е верига от две грешки: log injection, последвана от command injection. Коварното е, че атаката успява дори когато имейлът е отхвърлен.The mechanism is a chain of two flaws: log injection followed by command injection. The nasty part: the attack succeeds even when the email is rejected.

Сърцевината: данни, дошли директно от дневника, попадат в шел без никаква санитизация:The core: data taken straight from the log reaches a shell with no sanitization:

# Правилото, което хваща подхвърления ред:The rule that catches the injected line:
if (/: Service status change: (\S+) (.*) changed from stopped to running/)

# ... а стойността влиза в шел през backticks (опростено):... then the value enters a shell via backticks (simplified):
sub dosnmp {
    `$snmptrap ... s $args{SERVICE} ...`;   # ← $SERVICE идва НЕПРОВЕРЕНО от дневника← $SERVICE comes UNCHECKED from the log
}

Защо отхвърленият имейл все пак пробиваWhy a rejected email still gets through

Повечето администратори приемат, че щом postscreen върне 550 reject, заплахата е неутрализирана. Тук е обратното: самото логване на отказа е тригерът. Затова блокиране на подателя или spam филтри не помагат — вредата се случва в момента на записа в дневника.Most admins assume that once postscreen returns 550 reject, the threat is neutralized. Here it is the opposite: the logging of the rejection is the trigger. Blocking the sender or spam filters do not help — the damage happens at the moment of the log write.

Първият признак за успешно изпълнение е поява на файл-маркер (например version.txt) в публичната уеб директория /opt/zimbra/jetty*/webapps/zimbra/public/. Типичната следваща стъпка е качване на webshell.The first sign of successful execution is a marker file (e.g. version.txt) appearing in the public web directory /opt/zimbra/jetty*/webapps/zimbra/public/. The typical next step is dropping a webshell.

03Кой е засегнатWho is affected

УсловиеConditionУязвим?Vulnerable?
Zimbra под версия 10.1.20Zimbra below 10.1.20ДА / YES
Целият клон 8.8.15 (EOL)Entire 8.8.15 branch (EOL)ДА / YES
Клонове 9.x и 10.0.x (EOL)Branches 9.x and 10.0.x (EOL)ДА / YES
Zimbra 10.1.20 и по-новаZimbra 10.1.20 and newerНЕ / NO
snmp_notify=0НЕ / NO

Условието за експлоатация — snmp_notify включен (по подразбиране) и zmswatch работещ (също по подразбиране). Тоест типична инсталация е уязвима извън кутията.Exploitation requires snmp_notify enabled (default) and zmswatch running (also default). A typical install is vulnerable out of the box.

04Проверка дали сте уязвимиCheck if you are vulnerable

Изпълнете като root на пощенския сървър:Run as root on the mail server:

# версия — под 10.1.20 = потенциално уязвимversion — below 10.1.20 = potentially vulnerable
su - zimbra -c "zmcontrol -v"
# уязвимият превключвател — "yes" = уязвимthe vulnerable switch — "yes" = vulnerable
su - zimbra -c "zmlocalconfig snmp_notify"
# услугата, която експлоатираthe service that exploits it
su - zimbra -c "zmcontrol status" | grep -i swatch

Проверете и за вече качени следи — файлове, които не са част от Zimbra:Also check for existing compromise — files that are not part of Zimbra:

find /opt/zimbra/jetty*/webapps -name version.txt -path "*public*"
find /opt/zimbra/jetty*/webapps -name "*.jsp" -perm 640

Легитимните JSP файлове са с права 644. Файл с 640 в тези директории е силен индикатор за webshell.Legitimate JSP files have 644 permissions. A 640 file in those directories is a strong webshell indicator.

05МитигацияMitigation

Изключвате точно уязвимия SNMP-notify път. SMTP известията остават — те не са уязвими. Мониторингът продължава да работи.You disable exactly the vulnerable SNMP-notify path. SMTP notifications stay — they are not vulnerable. Monitoring keeps working.

su - zimbra -c "zmlocalconfig -e snmp_notify=0"
su - zimbra -c "zmswatchctl restart"

✓ Защо е безопасно✓ Why it is safe

Промяната не докосва доставката на поща, акаунтите или LDAP — пощата не може да падне от нея. Оцелява при рестарт. Рестартът на zmswatch регенерира вътрешния скрипт вече с изключен SNMP път.The change does not touch mail delivery, accounts or LDAP — mail cannot go down because of it. It survives reboot. Restarting zmswatch regenerates the internal script with the SNMP path now disabled.

06ВерификацияVerification

Първо — проверете, че регенерираният скрипт вече има SNMP изключен:First — confirm the regenerated script now has SNMP disabled:

NEW=$(ls -t /opt/zimbra/data/tmp/.swatchdog_script.* | head -1)
grep 'notifications{snmp}=' "$NEW"
# очаквано:expected:  $notifications{snmp}="0";

За категорично доказателство — безобиден тест. Ако митигацията работи, командата не се изпълнява (файлът-маркер не се създава):For definitive proof — a harmless test. If the mitigation works, the command does not execute (the marker file is not created):

rm -f /tmp/RCE_CANARY
printf '%s\n' 'Aug  1 12:00:00 mail t: Service status change: localhost $(touch /tmp/RCE_CANARY) changed from stopped to running' >> /var/log/zimbra.log
sleep 6
[ -e /tmp/RCE_CANARY ] && echo "STILL VULNERABLE" || echo "MITIGATION WORKS"
rm -f /tmp/RCE_CANARY

Единичните кавички около реда са важни — те пазят $(...) да не се разгъне от текущия шел, а да влезе буквално в дневника, точно както прави реалната атака.The single quotes around the line matter — they keep $(...) from being expanded by your current shell, so it enters the log literally, exactly as the real attack does.

07Локална защита: IDS + автономен guardLocal defense: IDS + autonomous guard

Митигацията затваря този конкретен път. Но EOL Zimbra има и други потенциални дупки без пач. Затова препоръчвам втори слой — host-based засичане и автономна реакция, работещо 24/7.The mitigation closes this specific path. But EOL Zimbra has other potential holes that will never be patched. So I recommend a second layer — host-based detection and autonomous response, running 24/7.

Скриптът може да се пусне през cron. Заменете адреса за известия и мрежите в белия списък със своите.The script can run via cron. Replace the alert address and whitelist networks with your own.

Guard — засича и автоматично елиминира заплахи (cron на 3 мин)Guard — detects and auto-eliminates threats (cron every 3 min)

Проектиран с едно правило: автоматични действия само когато са безопасни за пощата. Не докосва mailboxd, акаунти или LDAP.Built on one rule: automatic actions only when they are safe for mail. It never touches mailboxd, accounts or LDAP.

#!/bin/bash
# zimbra-guard.sh — засичане + активна реакция. Cron: */3 * * * *detection + active response. Cron: */3 * * * *
# Безопасно за пощата: НЕ пипа mailboxd/акаунти/LDAP.Mail-safe: does NOT touch mailboxd/accounts/LDAP.
set -uo pipefail
ALERT_TO="admin@YOUR-DOMAIN"            # ← сменетеchange
SENDMAIL=/opt/zimbra/common/sbin/sendmail
LOG=/var/log/zimbra-guard.log; QUAR=/root/quarantine
WEBAPPS=(/opt/zimbra/jetty_base/webapps /opt/zimbra/jetty/webapps)
mkdir -p "$QUAR"; A=""
act(){ echo "[$(date '+%F %T')] $1" >> "$LOG"; A+="$1
"; }

# 1. RCE-маркер version.txt → изтрийRCE marker version.txt → remove
for w in "${WEBAPPS[@]}"; do
  vt="$w/zimbra/public/version.txt"
  [ -e "$vt" ] && { cp -p "$vt" "$QUAR/version.$(date +%s)"; rm -f "$vt"; act "removed RCE marker $vt"; }
done

# 2. Webshell jsp (640 права или крипто код) → карантинаWebshell jsp (640 perms or crypto code) → quarantine
for f in $(grep -rlE "defineClass|javax\.crypto\.Cipher|getRuntime\(\)\.exec|ProcessBuilder" \
        "${WEBAPPS[@]}" --include="*.jsp" 2>/dev/null; \
        find "${WEBAPPS[@]}" -name "*.jsp" -perm 640 2>/dev/null | sort -u); do
  [ -e "$f" ] && { mv "$f" "$QUAR/$(basename $f).$(date +%s).quar"; act "quarantined webshell $f"; }
done

# 3. Митигацията върната? → върни snmp_notify=0Mitigation reverted? → restore snmp_notify=0
S=$(su - zimbra -c "zmlocalconfig -s -m nokey snmp_notify" 2>/dev/null | tr -d ' ')
[ "$S" != "0" ] && { su - zimbra -c "zmlocalconfig -e snmp_notify=0"; \
  su - zimbra -c "zmswatchctl restart"; act "restored snmp_notify=0"; }

# 4. fail2ban спрян? → рестартирайdown? → restart
systemctl is-active fail2ban >/dev/null 2>&1 || { systemctl restart fail2ban; act "restarted fail2ban"; }

# Доклад по имейл при действиеEmail report on any action
[ -n "$A" ] && printf "To: %s\nSubject: [GUARD] action on %s\n\n%s\n" \
  "$ALERT_TO" "$(hostname)" "$A" | "$SENDMAIL" -t 2>/dev/null

Cron записCron entry

# /etc/cron.d/zimbra-guard
*/3 * * * * root /usr/local/sbin/zimbra-guard.sh

IDS — засичане на промени спрямо baseline (cron на 5 мин)IDS — baseline-drift detection (cron every 5 min)

Guard-ът реагира на известните атаки. IDS-ът допълва с второ око: прави снимка (baseline) на чистото състояние и алармира при всяко отклонение — нови/променени JSP файлове, сменени SSH ключове, нов администраторски акаунт, нов UID 0. Само известява, не действа — затова е безопасно да следи неща, които са твърде рискови за автоматична реакция.The guard reacts to known attacks. The IDS adds a second eye: it snapshots the clean state (baseline) and alerts on any drift — new/changed JSP files, altered SSH keys, a new admin account, a new UID 0. It only alerts, never acts — so it can safely watch things too risky to auto-remediate.

#!/bin/bash
# zimbra-ids.sh — host-based IDS. Cron: */5 * * * *host-based IDS. Cron: */5 * * * *
#   zimbra-ids.sh --init   # baseline от чистото състояние# baseline from a clean state
#   zimbra-ids.sh          # проверка (за cron)# check (for cron)
set -uo pipefail
ALERT_TO="admin@YOUR-DOMAIN"; SENDMAIL=/opt/zimbra/common/sbin/sendmail
STATE=/var/lib/zimbra-ids; LOG=/var/log/zimbra-ids.log
WEBAPPS=(/opt/zimbra/jetty_base/webapps /opt/zimbra/jetty/webapps)
mkdir -p "$STATE"; F=""; add(){ F+="$1
"; }
jsps(){ find "${WEBAPPS[@]}" -name "*.jsp" 2>/dev/null | sort; }

# --- BASELINE ---
if [ "${1:-}" = "--init" ]; then
  jsps | xargs -r md5sum > "$STATE/jsp.md5"
  cat /root/.ssh/authorized_keys /home/*/.ssh/authorized_keys 2>/dev/null | md5sum > "$STATE/keys.md5"
  awk -F: '$3==0{print $1}' /etc/passwd | sort > "$STATE/uid0"
  su - zimbra -c "zmprov -l gaa -v" 2>/dev/null | awk '/^# name/{n=$3}/IsAdminAccount: TRUE/{print n}' | sort > "$STATE/admins"
  echo "baseline OK"; exit 0
fi

# --- ABSOLUTE IOC (винаги алармаalways alert) ---
for w in "${WEBAPPS[@]}"; do [ -e "$w/zimbra/public/version.txt" ] && add "RCE marker: $w/.../version.txt"; done
WS=$(grep -rlE "defineClass|javax\.crypto\.Cipher|getRuntime\(\)\.exec|ProcessBuilder" "${WEBAPPS[@]}" --include="*.jsp" 2>/dev/null)
[ -n "$WS" ] && add "webshell code: $WS"
P640=$(find "${WEBAPPS[@]}" -name "*.jsp" -perm 640 2>/dev/null); [ -n "$P640" ] && add "640-perm jsp: $P640"
[ "$(su - zimbra -c 'zmlocalconfig -s -m nokey snmp_notify' 2>/dev/null|tr -d ' ')" != "0" ] && add "snmp_notify reverted!"

# --- BASELINE DRIFT ---
D=$(diff <(sort "$STATE/jsp.md5") <(jsps | xargs -r md5sum | sort) | grep '^>'); [ -n "$D" ] && add "new/changed jsp: $D"
[ "$(cat /root/.ssh/authorized_keys /home/*/.ssh/authorized_keys 2>/dev/null|md5sum)" != "$(cat $STATE/keys.md5)" ] && add "SSH keys changed"
NU=$(comm -13 "$STATE/uid0" <(awk -F: '$3==0{print $1}' /etc/passwd|sort)); [ -n "$NU" ] && add "NEW UID 0: $NU"
NA=$(comm -13 "$STATE/admins" <(su - zimbra -c "zmprov -l gaa -v" 2>/dev/null|awk '/^# name/{n=$3}/IsAdminAccount: TRUE/{print n}'|sort)); [ -n "$NA" ] && add "NEW admin: $NA"

# --- аларма само при НОВИ находки (dedup)alert only on NEW findings (dedup) ---
if [ -n "$F" ]; then echo "[$(date '+%F %T')] $F" >> "$LOG"
  H=$(echo "$F" | md5sum | cut -d' ' -f1)
  [ "$H" != "$(cat $STATE/last 2>/dev/null)" ] && { echo "$H" > "$STATE/last"
    printf "To: %s\nSubject: [IDS] drift on %s\n\n%s\n" "$ALERT_TO" "$(hostname)" "$F" | "$SENDMAIL" -t 2>/dev/null; }
else rm -f "$STATE/last"; fi
# първо направете baseline от ЧИСТ сървър, после сложете cronfirst make a baseline from a CLEAN server, then add cron
/usr/local/sbin/zimbra-ids.sh --init
# /etc/cron.d/zimbra-ids
*/5 * * * * root /usr/local/sbin/zimbra-ids.sh

Ограничение, което трябва да знаетеA limitation you should know

Host-based защитата е първа линия, не единствена. Атакуващ с root може да я спре. Тя хваща автоматизираните атаки, но за по-силна гаранция baseline-ът трябва да е извън сървъра. Реалното решение остава обновяване до 10.1.20.Host-based defense is a first line, not the only one. An attacker with root can stop it. It catches automated attacks, but for a stronger guarantee the baseline should live off-host. The real fix remains upgrading to 10.1.20.

08Индикатори за компрометиране (threat hunting)Indicators of compromise (threat hunting)

Ако подозирате, че вече сте били атакувани, потърсете следите по-долу. Митигацията спира бъдещи атаки, но не изчиства вече качен код — затова проверката е отделна стъпка.If you suspect you were already attacked, hunt for the traces below. The mitigation stops future attacks but does not clean up already-planted code — so this check is a separate step.

1. Следи от изпълнение и webshells1. Execution traces & webshells

# маркер-файл, оставен при успешно RCE (не е част от Zimbra)marker file left by a successful RCE (not part of Zimbra)
find /opt/zimbra/jetty*/webapps -name version.txt -path "*public*" -ls

# JSP webshells — по права 640 (легитимните са 644)by 640 perms (legit are 644)
find /opt/zimbra/jetty*/webapps -name "*.jsp" -perm 640 -ls

# JSP с код за динамично зареждане на класове / криптиран payloadwith dynamic class loading / crypto payload
grep -rlE "defineClass|javax\.crypto\.Cipher|getRuntime\(\)\.exec|ProcessBuilder|BASE64Decoder" \
  /opt/zimbra/jetty*/webapps/*/public/ 2>/dev/null

# JSP, пипани в последните 14 дни (подправените дати са чест трик)JSP touched in the last 14 days (backdated timestamps are common)
find /opt/zimbra/jetty*/webapps -name "*.jsp" -mtime -14 -ls

2. Следи в дневниците на атаката2. Attack traces in the logs

# опити за инжекция (payload в адреса на получателя)injection attempts (payload in the recipient address)
grep -aE "Service status change.*\\\$\(" /var/log/zimbra.log*

# SNMP notification редове = swatch е обработил инжектиран редSNMP notification lines = swatch processed an injected line
grep -a "SNMP notification" /opt/zimbra/log/zmswatch.out*

# атакуващи IP-та — филтрирай по payload сигнатурата, после вземи IP от скобите.attacker IPs — filter by the payload signature, then take the IP from the brackets.
# Така хваща и двата reject формата: postscreen "from [IP]" И smtpd "from unknown[IP]".Catches both reject formats: postscreen "from [IP]" AND smtpd "from unknown[IP]".
grep -aE "Service status change:.*\\\$\(" /var/log/zimbra.log* \
  | grep -oE "from [a-zA-Z0-9._-]*\[[0-9.]+\]" | grep -oE "[0-9]{1,3}(\.[0-9]{1,3}){3}" \
  | sort | uniq -c | sort -rn | head

3. Персистентност (задържане на достъп)3. Persistence (foothold)

# скрити акаунти с root права (трябва да е само root)hidden root-privileged accounts (should be only root)
awk -F: '$3==0{print $1}' /etc/passwd

# подхвърлени SSH ключовеplanted SSH keys
for f in /root/.ssh/authorized_keys /home/*/.ssh/authorized_keys /opt/zimbra/.ssh/authorized_keys; do echo "== $f"; cat "$f" 2>/dev/null; done

# нови cron / systemd задачиnew cron / systemd jobs
crontab -l; crontab -l -u zimbra; ls -la /etc/cron.d/
find /etc/systemd/system -name "*.service" -mtime -30

# reverse shell / подозрителни процесиreverse shell / suspicious processes
ps auxww | grep -iE "base64 -d.*sh|nc -l|perl -e.*Socket|python.*socket" | grep -v grep

# изходящи връзки към непознати адресиoutbound connections to unknown hosts
ss -tnp state established | grep -vE "127.0.0.1|::1|ВАШАТА-МРЕЖА"

Ако намерите следаIf you find a trace

Не изтривайте веднага — първо запазете копие (forensics). Атакуващият с достъп до zimbra може да е прочел LDAP и сесийните ключове, затова приемете, че пощенските данни са изтекли: ротирайте zimbraAuthTokenKey, вътрешните пароли и потребителските пароли. Проверете за memory-resident код с рестарт на mailboxd. При съмнение — третирайте като пълно компрометиране на сървъра.Do not delete immediately — preserve a copy first (forensics). An attacker with zimbra access may have read LDAP and the session keys, so assume mail data leaked: rotate zimbraAuthTokenKey, internal passwords and user passwords. Check for memory-resident code with a mailboxd restart. When in doubt — treat it as a full server compromise.

09Траен фикс и проблемът с ембарготоPermanent fix & the embargo problem

Официалната поправка е Zimbra 10.1.20. Ако сте на поддържан клон — обновете.The official fix is Zimbra 10.1.20. If you are on a supported branch — upgrade.

За потребителите на безплатната (FOSS) версияFor free (FOSS) edition users

Zimbra публикува изходния код на FOSS версията с ембарго около 60 дни след платеното издание. 10.1.20 излезе в средата на юли 2026 г. — тоест FOSS build с този фикс се очаква чак около средата на септември 2026 г.Zimbra publishes the FOSS source with an embargo of about 60 days after the paid release. 10.1.20 shipped mid-July 2026 — so a FOSS build with this fix is not expected until around mid-September 2026.

Практическото следствие: всяка безплатна Zimbra версия в момента е уязвима и не може да се закърпи още около месец. Митигацията не е временно решение до пача — тя е единствената защита, която изобщо съществува днес за FOSS инсталациите.The practical consequence: every free Zimbra version right now is vulnerable and cannot be patched for about another month. The mitigation is not a stopgap until the patch — it is the only protection that exists today for FOSS installations.

Ако сте на EOL клон (8.8.15, 9.x, 10.0.x), официален пач няма да излезе изобщо. Митигацията е единствената защита, а дългосрочно единственият изход е миграция към поддържана версия.If you are on an EOL branch (8.8.15, 9.x, 10.0.x), no official patch will ever come. The mitigation is the only protection, and long-term the only way out is migrating to a supported version.

10Защо тази страницаWhy this page

Уязвимостта вече е публично оповестена от Zimbra и активно се експлоатира в реални условия. Тази страница е събрана от справянето с реален инцидент и е споделена, за да могат администраторите да защитят своите сървъри преди FOSS пачът да стане достъпен.The vulnerability is already publicly disclosed by Zimbra and actively exploited in the wild. This page was assembled while handling a real incident and shared so that administrators can protect their servers before the FOSS patch becomes available.

Умишлено тук няма готов за копиране експлойт — само механизмът, нужен за разбиране на риска, и пълната защита. Целта е защитна.There is deliberately no copy-paste exploit here — only the mechanism needed to understand the risk, and the full defense. The intent is defensive.

Ако управлявате Zimbra: приложете митигацията днес, добавете локалната защита, планирайте обновяването за септември. Ако познавате някого с Zimbra — препратете му това.If you run Zimbra: apply the mitigation today, add the local defense, plan the upgrade for September. If you know someone running Zimbra — forward this to them.