Проектное бюро на Дмитровском шоссе, 26 рабочих мест. В марте 2025-го их файрвол начал показывать, что почтовый сервер из демилитаризованной зоны сам стучится в конструкторскую сеть. Я неделю искал заражение, а нашёл штатную функцию и две февральские уязвимости, о которых на русском почти не пишут.
SQL-инъекция в ZimbraSync и SSRF в RSS: CVE-2025-25064 и 25065
Почтовый сервер ходит туда, куда ему нечего делать
В марте 2025-го мне написал главный инженер проектного бюро на Дмитровском шоссе. Двадцать шесть рабочих мест, чертежи, экспертиза, сеть разделена по-взрослому: конструкторские машины и хранилище с проектами в одном сегменте, почтовый сервер — в демилитаризованной зоне.
Их админ настроил на файрволе журналирование запретов и раз в неделю просматривал сводку. И увидел то, чего там быть не должно: полторы тысячи отброшенных пакетов за неделю с адреса почтового сервера в сторону внутренних сетей, в том числе в конструкторский сегмент. На порты 80, 8080 и 5000. По расписанию, ровно раз в двадцать минут.
Узнаваемо? У большинства компаний такой сводки нет вообще, и подобное живёт годами незамеченным. Если у вас почтовый сервер в отдельном сегменте, но исходящие из этого сегмента внутрь никто не смотрит — вы находитесь ровно в той же точке, просто без журнала.
Формулировка вопроса ко мне была: «нас взломали?». Ответ занял неделю и оказался длиннее, чем «да» или «нет».
Что показал сервер
Первым делом — версия и общая гигиена машины.
su - zimbra
zmcontrol -v
# Release 10.0.8_GA_4638.UBUNTU20_64 20240710 NETWORK edition
zmcontrol status | head -20
df -h /opt/zimbra
uptime
# load average: 1.12, 0.98, 0.91
Zimbra 10.0.8 Network Edition на Ubuntu 20.04 — лицензию бюро когда-то купило ради мобильной синхронизации и архива, и именно поэтому на сервере живёт ZimbraSync, о котором речь пойдёт ниже. Нагрузка нормальная, диск на 61%, службы все живые. Никакой ночной жары, никаких съеденных ядер — то есть картина совсем не такая, как при классическом майнере.
Версия 10.0.8 важна вот почему. Две уязвимости, закрытые вендором в феврале 2025 года, затрагивают именно этот диапазон:
| Уязвимость | Что это | Уязвимые версии |
|---|---|---|
| CVE-2025-25064 | SQL-инъекция в SOAP-эндпоинте ZimbraSync Service, CVSS 9.8 | 10.0.x до 10.0.12, 10.1.x до 10.1.4 |
| CVE-2025-25065 | SSRF в парсере RSS-фида | 9.0.0 до Patch 43, 10.0.x до 10.0.12, 10.1.x до 10.1.4 |
10.0.8 меньше 10.0.12 по обоим пунктам. Сервер попадал под обе.
Неделя, потраченная на неправильную версию
Я был уверен, что это заражение. Логика железная: машина из DMZ систематически сканирует внутреннюю сеть — значит, на ней сидит бот и ищет, куда расползтись. Я эту версию отрабатывал по полной программе.
ps -eo pid,ppid,user,pcpu,etime,cmd --sort=-pcpu | head -12
su - zimbra -c 'crontab -l'
crontab -l -u root
systemctl list-unit-files --state=enabled --type=service
ls -la /root/.ssh/ /etc/sudoers.d/
find / -xdev -type f -newermt "2025-02-01" -path '*/tmp/*' -o -path '*/dev/shm/*' 2>/dev/null
debsums -c 2>/dev/null | head
Чисто. Абсолютно. Ни одного лишнего процесса, ни одной чужой строки в кронах, ни одного включённого юнита, которого там быть не должно, ключей в authorized_keys нет, изменённых файлов пакетов нет. Я потратил на это четыре вечера и начал сомневаться уже в файрволе.
Развернуло меня расписание. Раз в двадцать минут — это не поведение сканера. Сканер ходит пачкой и затыкается. А раз в двадцать минут ходит планировщик. Что-то штатное, работающее по таймеру.
grep -iE "datasource|rss|feed" /opt/zimbra/log/mailbox.log | tail -20
zmprov gds proekt@byuro-dmitrovka.ru
# name: Отраслевые новости
# type: rss
# zimbraDataSourceEnabled: TRUE
# zimbraDataSourcePollingInterval: 20m
# zimbraDataSourceFolderId: 274
Вот оно. RSS-подписка, заведённая пользователем прямо в веб-почте. Сервер ходил по этому адресу раз в двадцать минут — как ему и положено. Только адрес в подписке был не тот, что человек вводил изначально.
Разбирались вдвоём с владельцем ящика, и история оказалась до обидного бытовой. Подписку он завёл ещё в 2021-м, на /rss.xml отраслевого портала. Портал в 2024-м переехал на новый движок, лента отвалилась, полгода в папке было пусто. В январе 2025-го человек решил починить: нашёл поиском страницу-агрегатор с кнопкой «подписаться на эту ленту», скопировал ссылку оттуда и вставил в Zimbra вместо старой. Ссылка вела не на ленту, а на редиректор вида r.агрегатор.tld/?u=.
То есть адрес не подменял ни злоумышленник, ни вредонос на сервере. Его поменял сам пользователь, добросовестно, своими руками. Первые отбитые пакеты на файрволе датированы 12 января — ровно тем днём, когда он это сделал.
Дальше начинается уязвимость. Редиректор отвечал 302 и уводил на посторонний хост, а тот отдавал валидный по формату RSS, в котором ссылки элементов вели не на статьи, а внутрь: на частные адреса с портами 80, 8080 и 5000. Три элемента в ленте — три запроса за один опрос. Адреса от опроса к опросу менялись: лента генерировалась на той стороне и перебирала частный диапазон вслепую, безо всякого знания о том, что за сеть окажется по ту сторону. В конструкторский сегмент 10.20.0.0/24 она попадала просто потому, что он тоже частный.
Арифметика сходится ровно. Двадцать минут — это 72 опроса в сутки, по три запроса на опрос: 216 отброшенных пакетов в день, 1512 за неделю — столько админ и увидел в своей сводке. За пятьдесят семь дней, с 12 января до моего приезда, набежало 12 312. Ни одного успешного: файрвол резал всё.
CVE-2025-25065: RSS-парсер как проход внутрь
Суть CVE-2025-25065 — SSRF в парсере RSS-фида. Server-Side Request Forgery, подделка запроса на стороне сервера. Уязвимость позволяет перенаправлять запросы на внутренние сетевые endpoint'ы.
Механика этого класса дыр устроена так. Вы даёте серверу адрес, по которому он должен что-то забрать. Сервер идёт по адресу. Проверка того, куда именно он идёт, либо отсутствует, либо обходится — через редирект, через нестандартную запись адреса, через ссылку внутри самого фида. И вот сервер, стоящий в вашей DMZ и имеющий доступ туда, куда снаружи никто не достучится, ходит по чужому указанию.
Для внутренних систем такой запрос выглядит абсолютно легитимным. Он приходит не из интернета — он приходит от почтового сервера. Собственного, доверенного, стоящего в списке разрешённых источников на половине внутренних сервисов.
Что в этой конкретной ситуации представляло ценность:
- Хранилище проектов по адресу 10.20.0.40 с веб-интерфейсом на порту 5000 без пароля во внутренней сети.
- Панель управления сетевого принтера, на которой лежал журнал сканирования — сканы паспортов и доверенностей.
- Внутренний портал с реестром договоров на 8080.
Ни к одному из них снаружи доступа не было. Через почтовый сервер — был бы, если бы файрвол не резал исходящие из DMZ внутрь. Их админ такое правило поставил три года назад, просто на всякий случай, и именно оно спасло ситуацию. Он же его и увидел в журнале.
Так что на вопрос «нас взломали?» честный ответ звучал так. Сервер не заражён — тут я неделю потратил зря и убедился в этом сам. Но эксплуатация SSRF снаружи была, самая настоящая, и шла пятьдесят семь дней подряд: чужая лента раз в двадцать минут заставляла почтовый сервер стучаться внутрь периметра. Не сработала она по одной причине — из-за правила на файрволе, поставленного три года назад «на всякий случай». Уберите это правило — и хранилище проектов на 10.20.0.40 читал бы кто угодно, кто умеет подсунуть ссылку в чужую RSS-подписку.
Для российских заказчиков: SSRF-уязвимость ZCS, вызванная недостаточной валидацией входящих запросов, учтена в банке данных ФСТЭК записью BDU:2024-07171. Если у вас аттестованная система, это ваш обязательный источник, а не заграничная новость.
CVE-2025-25064: девять и восемь из десяти
Вторая уязвимость в этой паре опаснее по оценке, но требует условия. CVE-2025-25064 — SQL-инъекция из-за недостаточной валидации входных данных в SOAP-эндпоинте ZimbraSync Service. Базовая оценка — CVSS 9.8.
Условие такое: эксплуатация выполняется аутентифицированным атакующим. То есть ему нужна рабочая учётная запись на вашем сервере. Результат — раскрытие метаданных писем и другой конфиденциальной информации.
Слово «метаданные» звучит успокаивающе, и зря. В проектном бюро метаданные переписки — это кто, кому, когда и с какой темой писал по каждому объекту. Из такого набора состав тендерной команды и график согласований восстанавливается без единой буквы текста писем.
И теперь про то, почему требование аутентификации — плохое утешение. Учётка добывается тремя способами, и все три банальны:
- Слабый пароль у любого из тридцати одного ящика. Достаточно одного.
- Учётка уволенного, которую не выключили.
- Пароль, утёкший в постороннюю утечку и переиспользованный на почте.
Мы проверяли третий вариант в первую очередь — как раз потому, что он самый частый:
# когда кто последний раз менял пароль
for u in $(zmprov -l gaa); do
echo -n "$u "
zmprov ga $u zimbraPasswordModifiedTime | awk '/ModifiedTime/{print $2}'
done | sort -k2
# кто вообще не логинился больше полугода — кандидаты на отключение
for u in $(zmprov -l gaa); do
echo -n "$u "
zmprov ga $u zimbraLastLogonTimestamp | awk '/Timestamp/{print $2}'
done | sort -k2 | head -12
Нашли четыре ящика, пароль на которых не менялся с 2019 года, и два ящика уволившихся в 2023-м, оставшихся активными. Следов эксплуатации SQL-инъекции не нашли — но я честно скажу, что и искать их было почти негде: детальный лог SOAP-запросов по умолчанию не пишется, а тот, что пишется, к моменту моего приезда провернулся.
Проверьте у себя за 2 минуты
Три команды на сервере. Плюс один взгляд на файрвол, который занимает больше двух минут, но стоит того.
su - zimbra
zmcontrol -v
for u in $(zmprov -l gaa); do zmprov gds $u 2>/dev/null | grep -q . && echo "DS: $u"; done
zmprov gc default | grep -iE "rss|feed|mobilesync"
| Что увидели | Как это читать |
|---|---|
| 10.0.x ниже 10.0.12 | Уязвимы обеими: и SQL-инъекцией, и SSRF |
| 10.1.x ниже 10.1.4 | Уязвимы обеими |
| 9.0.0 с патчем ниже 43 | Уязвимы SSRF. Патч 43 существует, поставить можно |
| 10.0.12 и выше либо 10.1.4 и выше | Обе закрыты |
| Вторая команда вернула хоть одну строку | У вас есть заведённые источники данных. Каждый надо посмотреть глазами: какой адрес и какой интервал опроса |
| На файрволе нет запрета «из сегмента почты во внутреннюю сеть» | Успешный SSRF вы не увидите никогда. Отброшенный — увидите |
Последняя строка — самая важная из всей таблицы. Не потому, что правило чинит уязвимость: оно её не чинит. А потому, что без него у вас нет вообще никакого сигнала о том, что что-то пошло не так.
Если апгрейд назначен на следующий квартал
Нормальная ситуация: обновление стоит в плане, бюджет на следующий квартал, дёргать сервер прямо сейчас никто не даст. Что можно сделать за один вечер.
- Запретить исходящие из сегмента почтового сервера во внутренние сети. Почтовому серверу внутрь нужны считанные вещи: контроллер домена, если авторизация идёт через него, и точка бэкапа. Всё остальное — закрыть. Это снимает практическую эксплуатируемость SSRF почти полностью.
- Убрать функцию внешних источников данных, если пользователям она не нужна. Ищем точное имя атрибута в своей версии и правим на уровне класса обслуживания.
- Удалить уже созданные подписки. Отдельным шагом, и вот об этом ниже отдельно.
- Отключить мобильную синхронизацию тем, кому она не нужна — сужает поверхность SOAP-эндпоинта.
- Провести ротацию паролей и погасить учётки уволенных. Это условие эксплуатации SQL-инъекции, и оно единственное, на которое вы влияете без обновления.
# смотрим, как называется атрибут именно в вашей версии
zmprov gc default | grep -i rss
# отключение функции на уровне COS (имя атрибута сверьте с выводом выше)
zmprov mc default zimbraFeatureRssEnabled FALSE
# мобильная синхронизация — только тем, кому реально нужна
zmprov mc default zimbraFeatureMobileSyncEnabled FALSE
zmprov ma direktor@byuro-dmitrovka.ru zimbraFeatureMobileSyncEnabled TRUE
# гасим учётки уволенных
zmprov ma ivanov@byuro-dmitrovka.ru zimbraAccountStatus closed
Строку с grep -i rss я ставлю первой намеренно. Имена атрибутов между ветками иногда расходятся, и подставлять команду из чужой статьи не глядя — верный способ получить непонятную ошибку и решить, что ничего не работает.
Флаг выключил, а оно продолжало ходить
Вот здесь я и сел в лужу, аккуратно и по-глупому.
Выключил функцию через класс обслуживания. Проверил, что в интерфейсе пункт добавления подписки пропал. Отчитался клиенту, поехал домой. Через два дня админ прислал новую выгрузку с файрвола — те же самые запросы, тот же интервал в двадцать минут.
Отключение функции убирает возможность создать новый источник данных. Уже созданные при этом никуда не деваются: они лежат в каталоге как объекты, привязанные к ящику, и планировщик продолжает их опрашивать по расписанию. Флаг фичи и существующие объекты — разные сущности, и я про это на ровном месте забыл.
# находим все существующие источники по всем ящикам
for u in $(zmprov -l gaa); do
out=$(zmprov gds $u 2>/dev/null | grep -E '^# name|zimbraDataSourceEnabled')
[ -n "$out" ] && echo "=== $u" && echo "$out"
done
# гасим конкретный источник
zmprov mds proekt@byuro-dmitrovka.ru "Отраслевые новости" zimbraDataSourceEnabled FALSE
# либо удаляем совсем
zmprov dds proekt@byuro-dmitrovka.ru "Отраслевые новости"
Всего таких источников по тридцати одному ящику нашлось семь. Три RSS-подписки, три внешних почтовых ящика, забираемых по POP3, и один календарный фид. Живыми и опрашивающимися были все семь. Самому старому было четыре года, и человек, который его завёл, уволился в 2023-м.
Осадок от этой истории у меня остался такой: любая настройка вида «выключить функцию» требует отдельной проверки, что стало с уже существующими объектами этой функции. Это верно не только для Zimbra и не только для RSS.
Сколько это заняло и что делать вам
По времени: 19 часов суммарно, из них 11 ушло на версию с заражением, которая не подтвердилась. Ещё 3 часа — повторный выезд из-за моей истории с флагом. Обновление с 10.0.8 до 10.0.12 сделали через три недели, в плановое окно, за 2 часа 15 минут с простоем почты 26 минут.
Клиенту это обошлось в 71 000 рублей, из которых, положа руку на сердце, тысяч двадцать — цена моей неправильной первой гипотезы. Я это честно снял со счёта наполовину, но время всё равно потрачено.
Теперь про то, чего в этой истории не видно со стороны.
- Обе уязвимости закрыты вендором в феврале 2025 года, и обновление в пределах ветки — процедура на пару часов. Технически это несложно.
- Сложно понять, эксплуатировали ли вас. По SSRF сигнал даёт только файрвол, и только если на нём настроено журналирование запретов. По SQL-инъекции сигнала нет почти никакого: детальный лог SOAP по умолчанию не ведётся.
- Сложно закрыться, не обновляясь. Меры выше работают, но каждая из них требует понимания, что именно она отключает и кому этим сломает работу.
- Сложно не пропустить остаточные объекты вроде тех семи источников данных. Их не видно ни в одном отчёте, пока не пройдёшься циклом по всем ящикам.
Ни на одну из этих двух уязвимостей на русском языке практически ничего не написано — при том что февраль 2025-го был давно, а серверов с версией ниже 10.0.12 в стране до сих пор много.
Если у вас десятая ветка или девятка — выполните три команды из блока самодиагностики, добавьте одну строчку про то, есть ли на файрволе запрет из сегмента почты внутрь, и пришлите мне. За день скажу, попадаете ли вы под обе уязвимости, что можно закрыть без обновления и сколько времени займёт апгрейд конкретно у вас.
Частые вопросы
У нас Zimbra 9. Эти уязвимости нас касаются?
Частично. SSRF в парсере RSS, то есть CVE-2025-25065, затрагивает версии 9.0.0 ниже Patch 43 — если у вас патч-уровень ниже, вы уязвимы, и лечится это установкой Patch 43. SQL-инъекция CVE-2025-25064 в опубликованных данных привязана к веткам 10.0.x до 10.0.12 и 10.1.x до 10.1.4, девятка в этом перечне не значится. Но я бы не расслаблялся: у девятки свой набор проблем, и вопрос жизненного цикла для неё стоит острее, чем вопрос отдельной CVE.
Что даёт злоумышленнику SSRF, если у него нет доступа в нашу сеть?
Ровно то, чего у него нет: доступ в вашу сеть руками вашего же сервера. Почтовый сервер обычно стоит в отдельном сегменте, но при этом имеет из него больше прав внутрь, чем случайная машина из интернета. Внутренние сервисы часто вообще не требуют аутентификации, потому что «они же во внутренней сети». Запрос от почтового сервера для них — свой. У моего клиента такими целями были хранилище чертежей с веб-панелью без пароля, портал договоров и панель сетевого принтера с журналом сканирования.
Эксплуатация SQL-инъекции требует учётки. Значит, риск невелик?
Требование аутентификации снижает риск, но не так сильно, как хочется. Учётная запись добывается тремя обычными способами: слабый пароль хотя бы у одного из ваших ящиков, незакрытая учётка уволенного сотрудника, переиспользованный пароль из посторонней утечки. У проектного бюро на тридцать один ящик нашлось четыре с паролем, не менявшимся с 2019 года, и две активные учётки людей, уволившихся в 2023-м. При оценке 9.8 по CVSS этого более чем достаточно.
Мы отключили RSS в настройках. Достаточно?
Нет, и я на этом сам споткнулся. Отключение функции на уровне класса обслуживания запрещает создавать новые источники данных, но уже существующие продолжают жить и опрашиваться планировщиком по своему расписанию. Их надо найти отдельно, пройдя циклом по всем ящикам командой zmprov gds, и погасить или удалить каждый. У моего клиента таких объектов нашлось семь по тридцати одному ящику, включая подписку четырёхлетней давности, заведённую человеком, который уволился два года назад.
Как вообще узнать, что почтовый сервер ходит куда не надо?
Только с файрвола, и только если на нём включено журналирование отброшенных пакетов между внутренними сегментами. Изнутри самого сервера это не видно никак: исходящие запросы делает штатный процесс mailboxd по штатному расписанию, они не отличаются от нормальной работы. Поэтому правило «из сегмента почтового сервера во внутреннюю сеть запрещено, кроме перечисленного» — это не столько защита, сколько датчик. У моего клиента такое правило стояло три года просто на всякий случай, и именно оно превратило невидимую проблему в строчку в еженедельной сводке.
Оставить комментарий