CVE-2024-45519: как письмо в поле CC превращается в RCE

Транспортная компания в Мытищах, 38 рабочих мест. В начале октября 2024-го их почтовый сервер начал по ночам жечь все четыре ядра. Разбираю по шагам, как поле CC обычного письма доехало до командной строки, что осталось в логах и почему я на вторые сутки получил повторное заражение уже вычищенной машины.

Ночью сервер работает, а утром отдыхает

Третьего октября 2024 года мне написал технический директор транспортной компании из Мытищ. Тридцать восемь рабочих мест, свой почтовый сервер, накладные и заявки на перевозку ходят почтой. Формулировка была такая: «ночью сервер греется, утром всё нормально, антивирус на нём ничего не находит».

Это очень узнаваемая жалоба. Не «почта не работает» — почта как раз работает. Просто машина, которая полтора года ровно тянула полсотни ящиков, вдруг стала ночью выдавать load average 14 при четырёх ядрах. Днём, когда люди начинали пользоваться почтой, нагрузка падала. Логика противоположная нормальной, и именно она обычно означает, что на сервере кто-то есть кроме вас.

Сервер стоял с 2019 года, обновлялся один раз. Пароля от админ-консоли никто не терял, документация была, бэкап делался. Всё выглядело прилично. Дыра оказалась ровно в одном месте — в патч-уровне.

Что было на входе

Первое, что я снимаю на подозрении на компрометацию, — версия. Не потому что это интересно, а потому что версия сразу отсекает половину гипотез.

su - zimbra
zmcontrol -v
# Release 9.0.0_GA_4517.RHEL7_64_20230210041355 FOSS edition Patch 9.0.0_P27

cat /etc/redhat-release
# CentOS Linux release 7.9.2009 (Core)

uptime
# 04:12:31 up 418 days,  2:44,  1 user,  load average: 13.91, 12.40, 9.88

Девятка, патч-уровень 27. А уязвимость CVE-2024-45519 закрыта вендором в 9.0.0 Patch 41. Разрыв в четырнадцать патч-уровней: судя по дате сборки в той же строке, P27 стоял с февраля 2023 года — то есть полтора года без обновлений безопасности. Для полноты картины остальные закрывающие версии: 8.8.15 Patch 46, 10.0.9 и 10.1.1.

Дальше смотрю процессы. Классика жанра:

ps -eo pid,ppid,user,pcpu,etime,cmd --sort=-pcpu | head -6
# 28914 1 zimbra 391 2-11:04 /opt/zimbra/common/lib/jvm/.sysd -c /tmp/.X11-lock/c.json
# 1741  1 zimbra   0.9 418-02 /opt/zimbra/common/sbin/slapd -l LOCAL0 ...

ls -la /opt/zimbra/common/lib/jvm/ | grep -v '^d'

Файл с точкой в начале имени, лежащий среди библиотек JVM, запущенный от пользователя zimbra, съедающий четыре ядра. Майнер. Дальше вопрос был один: как он туда попал.

Версия, которую я проверил и выбросил

Первая мысль была скучная: увели пароль. Кто-то из водителей отдал учётку фишингу, злоумышленник зашёл в веб-почту, и дальше как-то раскрутил. Такое случается чаще, чем эксплуатация свежих уязвимостей, и проверяется быстро.

grep -i "authentication failed" /opt/zimbra/log/mailbox.log* | wc -l
# 1206

grep -iE "soap|imap|http" /opt/zimbra/log/mailbox.log* \
  | grep -i "success" | grep -oP 'oip=\K[0-9.]+' \
  | sort | uniq -c | sort -rn | head

Все успешные входы — с офисного адреса и с домашних IP сотрудников, ни одного заграничного, ни одного хостингового. Неудачных попыток 1206 за месяц, что для сервера, торчащего наружу веб-почтой, вообще ничто. Версия с угнанным паролем не подтвердилась, и я её отбросил.

Вторая мысль была умнее и тоже оказалась мимо: открытый релей. Если zimbraMtaMyNetworks настроен криво, сервер начинает пересылать чужую почту, и через него льют что угодно. Проверил:

zmprov gacf zimbraMtaMyNetworks
# zimbraMtaMyNetworks: 127.0.0.0/8 [::1]/128 192.168.24.0/24

Чисто. Ни одной лишней сети. Я даже расстроился — красивая версия была. Значит, вход был не через аутентификацию и не через релей, а через что-то, что вообще не спрашивает пароля.

Порт 10027 и поле CC

Внутри Zimbra письмо после приёма гоняется по цепочке служб, каждая из которых слушает свой локальный порт. Одна из них — postjournal, служба журналирования почты. Она принимает входящие сообщения по SMTP на порту 10027.

Суть CVE-2024-45519 — отсутствие санитизации пользовательского ввода. Адреса получателей из SMTP-команды RCPT TO склеиваются в строку и уходят в системный вызов запуска процесса без экранирования. То есть текст, который пришёл в шапке письма, исполняется на сервере как команда оболочки. Аутентификация при этом не нужна вообще: письмо просто приходит снаружи, как любое другое письмо.

Атакующие в дикой природе делали это так: подставляли фальшивые адреса Gmail в качестве отправителя, а полезную нагрузку прятали в адресах поля CC — она и доезжала до RCPT TO. Команда была закодирована в base64 и заканчивалась конструкцией вида | base64 -d | sh, то есть раскодировала и запускала себя сама. Всё.

# проверяем, слушает ли служба
ss -lntp | grep 10027
# LISTEN 0 100 127.0.0.1:10027 0.0.0.0:* users:(("postjournal",pid=2210,fd=6))

zmlocalconfig | grep -i postjournal
# postjournal_enabled = true

grep -n 10027 /opt/zimbra/conf/master.cf.in

Служба была включена, хотя журналированием почты в этой компании никто никогда не пользовался. Она просто досталась по наследству от конфигурации по умолчанию — и это, судя по всему, самая массовая история среди пострадавших.

Таймлайн, по которому считается риск

  • 4 сентября 2024 — вендор выпускает патч.
  • 27 сентября 2024 — публикуется PoC-эксплойт.
  • 28 сентября 2024 — зафиксированы первые атаки. На следующий день.
  • 1 октября 2024 — HarfangLab подтверждает массовую эксплуатацию.

По данным Proofpoint, злоумышленники пользовались уязвимостью ещё до огласки, несколько недель. Мой клиент словил её в ночь на 30 сентября — на четвёртый день после публикации PoC и через двадцать шесть дней после выхода патча, который никто не поставил.

Следы в логах

Хорошая новость: эксплуатация оставляет следы, и они читаемые. Смотреть надо в транспортный лог, а не в mailbox.log — там про это ничего не будет.

zgrep -h "postjournal" /var/log/zimbra.log* | grep -v "starting\|stopping" | head
# Sep 30 03:41:12 mail postjournal[2210]: warning: unknown recipient format
# Sep 30 03:41:12 mail postjournal[2210]: sh: -c: line 0: syntax error near unexpected token
# Sep 30 03:41:14 mail postjournal[2210]: message accepted for delivery

zgrep -hE "from=<[a-z0-9]+@gmail\.com>.*to=<.*\$\(" /var/log/zimbra.log*
zgrep -h "base64 -d" /var/log/zimbra.log* /opt/zimbra/log/*.log

Ключевой маркер — строки от postjournal с синтаксическими ошибками шелла. Оболочка ругается на кривую команду и пишет это в тот же лог. То есть в журнале буквально видно, как сервер пытался выполнить то, что ему прислали в письме, и на части попыток спотыкался.

Дальше я смотрел, что появилось на диске за нужные сутки:

find /opt/zimbra /tmp /var/tmp /dev/shm -xdev \
  -newermt "2024-09-29 20:00" ! -newermt "2024-10-01 06:00" \
  -type f -printf '%T+ %s %p\n' 2>/dev/null | sort | head -40

su - zimbra -c 'crontab -l'
# */11 * * * * /opt/zimbra/common/lib/jvm/.sysd -c /tmp/.X11-lock/c.json

Крон пользователя zimbra, майнер, конфиг в /tmp под именем, похожим на файл блокировки X-сервера. Ничего изобретательного. Изобретательность была в другом месте, и я до неё дошёл только через сутки.

Проверьте у себя за 2 минуты

Три команды. От пользователя zimbra, ничего не меняют.

su - zimbra
zmcontrol -v
zmlocalconfig | grep -i postjournal
zgrep -c "postjournal" /var/log/zimbra.log* 2>/dev/null | grep -v ':0$'
РезультатЧто это значит
8.8.15 с патчем ниже 46Уязвимы. Плюс версия снята с поддержки — патча выше не будет в принципе
9.0.0 с патчем ниже 41Уязвимы. Патч существует, поставить можно
10.0.x ниже 10.0.9 или 10.1.0Уязвимы. Обновление в пределах ветки
10.0.9 и выше, 10.1.1 и вышеКонкретно эта дыра закрыта. Остальные — отдельный разговор
postjournal_enabled = true, а журналированием вы не пользуетесьРаботает служба, которая вам не нужна. Отключать
Третья команда что-то нашла в старых логахЧитать эти строки глазами. Синтаксические ошибки шелла в них — повод считать сервер скомпрометированным

Для российских заказчиков добавлю: уязвимость службы postjournal учтена в банке данных ФСТЭК записями BDU:2021-04615 и BDU:2022-04036. Если у вас аттестованная информационная система, это не абстрактная новость с зарубежного сайта, а запись в источнике, которым вы обязаны пользоваться при оценке угроз.

Что мы сделали в ту ночь

Порядок был такой, и он важен именно порядком.

  1. Отрезали машину от интернета. Не выключили — отрезали. Выключенный сервер уносит с собой всё, что жило в памяти, а нам нужны были соединения.
  2. Сняли слепок. ss -tanp, список процессов, открытые файлы, копия /var/log и /opt/zimbra/log на внешний диск. Пятнадцать минут работы, которые потом сэкономили полдня.
  3. Отключили postjournal — как временную меру до патча.
  4. Проверили mynetworks ещё раз, уже прицельно.
  5. Поставили патч. С 9.0.0 Patch 27 до Patch 41 — в пределах ветки, без смены мажорной версии.
  6. Сменили все пароли: админ-консоль, zimbra_ldap_password, ldap_root_password, все пользовательские.
zmlocalconfig -e postjournal_enabled=false
zmmtactl restart

# патч ставится от root после распаковки архива нужного уровня
./installPatch.sh
zmcontrol restart
zmcontrol -v
# Release 9.0.0_GA_4517.RHEL7_64_20230210041355 FOSS edition Patch 9.0.0_P41

Патч встал за сорок минут вместе с рестартом. Почта не ходила девятнадцать минут. Ни одно письмо не потерялось — входящие в это время встают в очередь на стороне отправителя и доезжают позже, это нормальное поведение SMTP.

Тридцать один час — и всё вернулось

Я уехал в пятницу утром довольный. В субботу вечером мониторинг снова показал нагрузку.

Вот здесь я и ошибся. Я вычистил то, что нашёл: крон пользователя zimbra, бинарник в каталоге JVM, конфиг в /tmp. Проверил ещё раз крон, ещё раз каталоги. И не проверил systemd. А закладка была именно там — юнит с безобидным именем, включённый в автозапуск от root, который раз в сутки восстанавливал майнер из скачиваемого архива.

systemctl list-unit-files --state=enabled --type=service | grep -vE 'systemd|dbus|network|sshd|chrony|zimbra'
# journallogging.service    enabled

systemctl cat journallogging.service
# ExecStart=/lib/udev/.logsvc

stat -c '%y %n' /lib/udev/.logsvc /etc/systemd/system/journallogging.service

Имя подобрано так, чтобы взгляд по нему проскальзывал. Рядом с настоящими systemd-journald и rsyslog строка journallogging не цепляет вообще. Файл лежал в /lib/udev с точкой в начале имени и датой изменения, подделанной под соседние файлы пакета.

Из этого я вынес правило, которым пользуюсь до сих пор. Если атакующий получил выполнение команд от пользователя zimbra, дальше он почти наверняка попробовал подняться до root. И пока вы не проверили список включённых юнитов, содержимое /etc/systemd/system, ключи в /root/.ssh/authorized_keys и файлы в /etc/sudoers.d, чистка не закончена. Она даже не начата.

Второй заход занял ещё шесть часов. По совокупности я до сих пор считаю, что правильным решением было бы поднять новую машину с нуля и перенести на неё только почту. Мы этого не сделали, потому что клиент вёз груз и просил не останавливать переписку. Компромисс сработал, но повторять его я не люблю.

Во что это обошлось и что делать вам

Считаем честно. Работа: 22 часа, из них 6 — второй заход после перезаражения. Простоев почты суммарно 41 минута. Смена паролей 52 ящикам — это отдельная головная боль для тридцати восьми человек, половина из которых водители с телефонами. Три дня после инцидента наш инженер сидел на мониторинге исходящей почты, потому что мы не были уверены, что с сервера не улетал спам.

Прямые затраты клиента — 143 000 рублей. Патч, который всё это снимал, вышел 4 сентября 2024 года и ставился за сорок минут. Разница между сорока минутами в сентябре и двадцатью двумя часами в октябре — это и есть цена откладывания.

По данным СКИПА CyberOK, в России на момент огласки было больше 10 000 инсталляций Zimbra и свыше 3000 уязвимых IP-адресов. Не десять и не сто. Три тысячи.

Теперь про сложность, честно. Поставить патч в пределах ветки — операция несложная, инструкция открытая, справится любой администратор. Тяжело другое.

  • Определить, эксплуатировали ли вас уже. Логи ротируются, и если постоять неделю, читать будет нечего.
  • Понять, докуда добрался атакующий. Пользователь zimbra — это ещё не root, но от него до root на CentOS 7 обычно недалеко.
  • Не пропустить второй уровень закладки. Я пропустил, при том что смотрел внимательно.
  • Решить, чистить или переставлять. Это решение принимается по объёму найденного, а не по желанию сэкономить.

Если ваш сервер попадает в уязвимые версии по таблице выше — сделайте три вещи из блока самодиагностики и пришлите мне вывод. Скажу за день, есть ли у вас следы эксплуатации, чем это лечится в вашей конкретной версии и сколько времени займёт. Три команды, ничего не ломается, ничего не перезапускается.

Частые вопросы

Можно ли просто отключить postjournal и не обновляться?

Как временная мера — да, вендор сам её и рекомендовал: отключить службу, если журналирование почты вам не нужно, и заодно проверить корректность zimbraMtaMyNetworks. Делается это командой zmlocalconfig -e postjournal_enabled=false с последующим рестартом MTA. Но это затычка на одну конкретную дыру, а не решение. Патч 9.0.0 Patch 41 закрывает не только её. Я советую отключить службу прямо сейчас, если она вам не нужна, а патч поставить в ближайшее окно — обычно это одна ночь работы.

Как понять, что уязвимость уже использовали против нас?

Смотреть в /var/log/zimbra.log и его архивы за период с конца сентября 2024 года. Ищите строки от процесса postjournal, особенно с ошибками синтаксиса шелла — это буквально следы того, как оболочка споткнулась на присланной команде. Дальше проверяйте, что появилось на диске: find по /opt/zimbra, /tmp, /var/tmp и /dev/shm с фильтром по дате изменения. И обязательно смотрите crontab пользователя zimbra и список включённых systemd-юнитов. Если логи уже провернулись и ничего не осталось — это не доказательство чистоты, это отсутствие данных.

Наш сервер за NAT и порт 10027 наружу не проброшен. Мы в безопасности?

Нет. Порт 10027 слушается на локальном интерфейсе, и пробрасывать его никто не собирается. Письмо приходит на обычный порт 25 снаружи, как любое другое письмо, а до уязвимой службы доезжает уже по внутреннему транспорту Zimbra. То есть достаточно того, что ваш сервер принимает почту из интернета — а он её принимает, иначе зачем он вам. Именно поэтому уязвимость и получила такую оценку: эксплуатация не требует ни аутентификации, ни доступа к внутренней сети.

Что делать, если версия 8.8.15 и обновляться уже некуда?

Для 8.8.15 патч, закрывающий эту уязвимость, существует — это Patch 46. Поставить его можно. Проблема в другом: общая поддержка 8.8.15 Open Source Edition закончилась 31 декабря 2023 года, и следующей дыры вам уже не закроют. Так что порядок действий такой: сначала ставите Patch 46, чтобы закрыть текущий пожар, а параллельно начинаете считать переезд — на десятую ветку, на Carbonio или на другую платформу. Патч покупает вам время на планирование, но не решает вопрос жизненного цикла.

Мы вычистили майнер, нагрузка ушла. Можно считать инцидент закрытым?

Только после того, как вы проверили второй уровень. У меня в Мытищах сервер вернулся к жизни через тридцать один час, потому что кроме крона и бинарника в каталоге JVM был ещё systemd-юнит от root с именем journallogging.service, восстанавливавший всё обратно. Минимальный список проверки: systemctl list-unit-files --state=enabled, содержимое /etc/systemd/system, /root/.ssh/authorized_keys, /etc/sudoers.d, файлы с изменённой датой в системных каталогах. Если хоть что-то из этого выглядит незнакомым — считайте, что root у атакующего был.

Нужна помощь с проектом?

Специалисты АйТи Фреш помогут с архитектурой, DevOps, безопасностью и разработкой — 15+ лет опыта

📞 Связаться с нами
#Zimbra#уязвимости#RCE#форензика#postjournal
Комментарии 0

Оставить комментарий

загрузка...

Подпишитесь на рассылку ITfresh

Раз в неделю — практические гайды для руководителя IT и сисадмина: безопасность, 1С, миграции, резервные копии, лайфхаки из реальных проектов.

Реквизиты оператора персональных данных

ООО «АЙТИ-ФРЕШ», ИНН 7719418495, КПП 771901001. Юридический адрес: 105523, г. Москва, Щёлковское шоссе, д. 92, корп. 7. Контакт: info@itfresh.ru, +7 903 729-62-41. Оператор обрабатывает e-mail подписчика в целях рассылки информационных и рекламных материалов до момента отзыва согласия.