Гостиница в Подольске на тридцать рабочих мест осталась с почтовым сервером, к которому никто не знал пароля. Root по SSH был, админ-консоль — закрыта, документации ноль. Разбираю порядок команд, которым я за день восстановил полную картину: домены, ящики, алиасы, рассылки, кроны и бэкапы. И где я в тот раз крепко ошибся.
Админ уволился, паролей нет: как за день описать чужую Zimbra
Сервер работает, а что внутри — не знает никто
Мне позвонили в четверг вечером, в июне 2021 года. Гостиница в Подольске, тридцать рабочих мест, собственный почтовый сервер в серверной рядом со стойкой видеонаблюдения. Системный администратор уволился тремя неделями раньше. Пароль от админ-консоли Zimbra он назвал управляющей устно, та записала его на стикере, стикер уехал вместе со списанным монитором.
Почта при этом ходила. Брони падали в reception@, счета уходили, никто ничего не замечал. Тревога началась в тот момент, когда бухгалтерии понадобился новый ящик — и оказалось, что завести его некому и нечем.
Если у вас сейчас похоже — сервер стоит с позапрошлого директора, работает, трогать боятся, а кто и что на нём настраивал, не помнит никто — дальше про вас. Ситуация чинится. Но чинится не «сбросом пароля», а полной описью того, что там вообще живёт.
Мне отдали одну вещь: root по SSH. Бывший админ передал его вместе с ноутбуком, и это оказалось единственным, что вообще уцелело из доступов. Этого хватило.
Что было на входе
Приехал в пятницу к десяти утра. Сервер — обычный tower, восемь гигабайт памяти, диск на 500 ГБ, занято 78%. CentOS 7.6. Первое, что я делаю на чужой машине, — снимаю версию и историю установок, потому что версия определяет вообще всё остальное.
su - zimbra
zmcontrol -v
# Release 8.8.15_GA_3869.RHEL7_64_20190917004220 RHEL7_64 FOSS edition
zmhostname
# mail.hotel-podolsk.local
cat /opt/zimbra/.install_history
Строку я, к слову, сначала записал в блокнот по памяти и с ошибкой — рука сама дописала привычную убунтовскую сборку вместо RHEL-овской. Через час сверял с экраном и переписывал заново. На чужом сервере такие мелочи потом стоят часа, поэтому дословно: копируем, а не запоминаем.
Файл .install_history — это то место, которому я верю больше, чем людям. Там построчно записано, когда и какой пакет ставили или обновляли. У этого сервера история начиналась 14 марта 2018 года с версии 8.7.11, потом апгрейд до 8.8.8, потом до 8.8.15 в феврале 2020-го. И тишина полтора года.
Дальше — какие роли реально включены. Люди часто думают, что у них «просто почта», а на машине висит ещё и прокси, и LDAP-мастер, и MTA, и всё это надо описывать отдельно.
zmprov gs $(zmhostname) zimbraServiceEnabled
# zimbraServiceEnabled: ldap
# zimbraServiceEnabled: logger
# zimbraServiceEnabled: mailbox
# zimbraServiceEnabled: mta
# zimbraServiceEnabled: proxy
# zimbraServiceEnabled: snmp
# zimbraServiceEnabled: stats
zmcontrol status
zmvolume -l
Одна коробка, все роли на ней. Для тридцати мест это нормально. Тома: primary на /opt/zimbra/store, index там же, secondary нет, HSM никто не настраивал. Значит, весь рост упирается в один физический диск — и 78% занятости из первой строчки внезапно становятся цифрой, о которой стоит подумать отдельно.
Первый час: каркас без всякой админки
Админ-консоль мне не нужна. Вся картина снимается из командной строки, и снимается быстрее, чем кликается.
Домены и учётки:
zmprov gad
# hotel-podolsk.ru
# podolsk-hotel.ru
# hotel-podolsk.local
# restoran-podolsk.ru
zmprov -l gaa | wc -l
# 47
zmprov gaaa
# admin@hotel-podolsk.local
# galsync@hotel-podolsk.local
# it@hotel-podolsk.ru
Сорок семь ящиков на тридцать человек — обычная история: сервисные адреса, отделы, два ящика уволенных, которые никто не выключил. Администраторов трое, и третий, it@, — это личный ящик того самого уволившегося. Права полного администратора на нём остались.
Флаг -l в zmprov -l gaa заставляет утилиту ходить напрямую в LDAP, минуя SOAP-сервис. На вялой машине это разница между двумя секундами и сорока. И работает даже тогда, когда mailboxd прилёг.
Рассылки, алиасы и классы обслуживания:
zmprov gadl # все списки рассылки
zmprov gdl reception@hotel-podolsk.ru # состав конкретного списка
zmprov gac # все COS
for u in $(zmprov -l gaa); do
echo -n "$u : "
zmprov ga $u zimbraMailAlias | grep -c zimbraMailAlias:
done
Насчитал шесть рассылок и тридцать один алиас. Треть алиасов вела на ящики людей, которые в гостинице уже не работали. Это не проблема безопасности сама по себе, но это ровно тот мусор, который через год превращается в «а почему письмо от турагентства никто не увидел».
Пароли: то, что нельзя прочитать, но можно унести
Пароли пользователей в Zimbra хранятся хэшами в атрибуте userPassword. Прочитать их как текст нельзя, а вот забрать целиком и перенести на другой сервер — можно. Это ключевой момент, если завтра встанет вопрос переезда: люди не заметят смены платформы, если пароли доедут.
mkdir -p /migration/passwords && cd /migration/passwords
for user in $(zmprov -l gaa); do
zmprov -l ga $user userPassword | grep userPassword: \
| awk '{print $2}' > $user.shadow
done
wc -l *.shadow | tail -1
А вот служебный пароль LDAP лежит в открытом виде, и это надо знать про свой сервер:
zmlocalconfig -s zimbra_ldap_password
zmlocalconfig -s ldap_root_password
ldapsearch -x -H ldap://localhost:389 \
-D "uid=zimbra,cn=admins,cn=zimbra" -w "$(zmlocalconfig -s zimbra_ldap_password | awk '{print $3}')" \
-b "" -s base "objectclass=*"
Ключ -s у zmlocalconfig показывает секретные значения вместо звёздочек. Любой, кто получил шелл от пользователя zimbra, читает эти пароли за одну команду. Отсюда простое следствие: доступ к учётке zimbra на сервере равен доступу ко всему каталогу целиком. Я к этому вернусь в конце.
Проверьте у себя за 2 минуты
Четыре команды. Выполняются от пользователя zimbra, ничего не меняют, ничего не перезапускают.
su - zimbra
zmcontrol -v
zmprov -l gaa | wc -l
zmprov gaaa
crontab -l | grep -v '^#' | grep -v '^$'
| Что увидели | Как это читать |
|---|---|
| Версия 8.8.15 без слова Patch в строке | Сервер ни разу не патчили после установки. Патч-уровень нулевой |
| Число ящиков больше числа сотрудников в полтора раза | Есть забытые учётки уволенных — их надо закрыть отдельным списком |
В gaaa есть личный ящик человека, который у вас не работает | Права администратора остались у постороннего. Это первое, что надо снять |
| В crontab пусто или только строки Zimbra | Собственного бэкапа нет вообще. Zimbra OSE своего инструмента резервного копирования не имеет |
| В crontab есть чужая строка со скриптом | Ничего не доказывает. Надо открыть скрипт и посмотреть, куда он реально пишет и когда писал в последний раз |
Последняя строка таблицы — то, на чём я в тот раз и обжёгся.
Комментарий в crontab, которому я поверил
В кроне у пользователя zimbra висела строка. Аккуратная, с комментарием на русском:
# nightly backup to NAS 192.168.10.20 -- do not touch
0 2 * * * /opt/scripts/zmbackup.sh >> /var/log/zmbackup.log 2>&1
Я прочитал, поставил галочку «бэкап есть» и пошёл дальше — снимать конфиги MTA и сертификаты. Полдня работал в спокойном режиме, потому что считал, что откатиться в случае чего есть куда. Это была ошибка, и довольно дорогая по нервам.
К вечеру дошли руки открыть сам скрипт. Внутри — цикл по ящикам с zmmailbox getRestURL, выгрузка в /mnt/nas/zimbra, потом ротация с удалением архивов старше четырнадцати дней. Написано грамотно. Только вот:
mount | grep nas
# пусто
tail -3 /var/log/zmbackup.log
# 2021-06-09 02:00:04 ERROR: /mnt/nas is not a mount point, abort
# 2021-06-10 02:00:03 ERROR: /mnt/nas is not a mount point, abort
# 2021-06-11 02:00:04 ERROR: /mnt/nas is not a mount point, abort
grep -m1 ERROR /var/log/zmbackup.log
# 2021-02-11 02:00:05 ERROR: /mnt/nas is not a mount point, abort
ls -la /mnt/nas/zimbra 2>/dev/null | head
# ls: cannot access '/mnt/nas/zimbra': No such file or directory
Хвост лога — три одинаковые строки за три последние ночи, включая ночь накануне моего приезда. Первую такую строку я нашёл отдельной командой, и она датирована 11 февраля. Между ними — сплошняк, ни одной успешной ночи.
NAS отвалился 11 февраля. Скрипт исправно падал сто двадцать одну ночь подряд, писал ошибку в лог, который никто не читал, и никому ни разу не пожаловался. Последний живой бэкап был датирован 10 февраля 2021 года — то есть на момент моего приезда ему было сто двадцать дней, и лежал он на файловой шаре, которой больше не существовало.
Мораль простая и неприятная. Строка в crontab доказывает существование строки в crontab. Больше ничего. Проверять надо не расписание, а последний успешно созданный файл и его размер.
Как я вернул админку и что при этом сломал
Пароль администратора в Zimbra меняется одной командой и без остановки служб. Никаких single-user режимов, никаких перезагрузок:
zmprov sp admin@hotel-podolsk.local 'НовыйДлинныйПароль2021!'
zmprov ga admin@hotel-podolsk.local zimbraIsAdminAccount
# zimbraIsAdminAccount: TRUE
Зашёл в консоль на https://mail:7071/zimbraAdmin, всё открылось. Красиво.
А в понедельник утром администратор гостиницы написала, что перестала работать выгрузка броней в 1С. Оказалось, интеграционный скрипт бронирования логинился в почту под тем самым admin@ — пароль был зашит в конфиг обменника прямым текстом. Я его сменил, обменник упёрся в 401 и молча перестал забирать письма из booking@. За выходные накопилось 187 непрочитанных писем с заявками.
Ничего не потерялось, письма никуда не делись, но два часа в понедельник ушло на поиск источника и правку конфига. Могло не уйти. С тех пор перед сменой любого пароля на принятом сервере я сначала смотрю, кто этим аккаунтом ходит:
grep -i "admin@" /opt/zimbra/log/mailbox.log | grep -i "oip=" | \
awk -F'oip=' '{print $2}' | awk -F';' '{print $1}' | sort | uniq -c | sort -rn
Команда вытаскивает из журнала все IP, с которых входили под этим ящиком. Если там кроме рабочих станций светится адрес сервера 1С или какой-нибудь принтер — вы нашли зависимость до того, как её сломали, а не после.
Чем закончилось и во что обошлось
Полная опись заняла девять часов чистого времени, разнесённых на два дня. На выходе клиент получил документ на одиннадцать страниц: четыре домена, 47 ящиков с указанием владельцев, шесть рассылок, 31 алиас, три администратора, полный список сервисов, схема портов, где лежат сертификаты и когда они истекают, где хранятся пароли и что с бэкапом.
Работа обошлась в 34 000 рублей. Теперь считаем альтернативу.
Диск был занят на 78% и рос примерно на два гигабайта в неделю. Zimbra при заполнении диска останавливается сама — не «замедляется», а встаёт: MTA начинает отвечать отправителям «452 4.3.1 Insufficient system storage», а сторож конфигурации по достижении критического порога, а это где-то за 95% занятости, гасит службы. Считаем: из 500 ГБ свободно 110, до тех самых 95% остаётся 85 ГБ, при двух гигабайтах в неделю это сорок с небольшим недель. Меньше года. Живого бэкапа не было сто двадцать дней. Развернуть почту гостиницы с нуля, без архива переписки, — это трое суток работы двух человек плюс безвозвратная потеря истории броней и переписки с турагентствами.
Теперь то же самое в рублях, считаю грубо, но по их же цифрам. Шестьдесят номеров, летняя загрузка около 80%, средний тариф 4 700 ₽ — это примерно 225 000 ₽ выручки в сутки. Почтой к ним приходило меньше половины броней, остальное телефон и агрегаторы, так что трое суток без почты в сезон — порядка 300 000 ₽ несостоявшихся заселений. Плюс само восстановление: 48 человеко-часов по нашей ставке 3 500 ₽ — ещё 168 000 ₽. Итого без малого полмиллиона против 34 000 ₽ за опись, которую можно было заказать в любой спокойный вторник.
Плюс мелочь, которая мелочью не является: бывший сотрудник сохранял права полного администратора три недели после увольнения. Мы их сняли в первый же день. Я не утверждаю, что он собирался ими воспользоваться. Утверждаю, что проверить это было невозможно — журналы аудита за прошлый месяц уже провернулись.
Что делать до того, как админ уволится
Если вы читаете это и у вас ещё есть свой администратор — попросите его выполнить пять вещей и приложить вывод в общую папку. Это займёт у него полчаса.
- Вывод
zmcontrol -vиcat /opt/zimbra/.install_history— версия и вся история апгрейдов. - Списки:
zmprov gad,zmprov -l gaa,zmprov gaaa,zmprov gadl. - Пароли
zimbra_ldap_password,ldap_root_password, админ-консоли — в парольное хранилище компании, а не в файл на рабочем столе. - Скрипт бэкапа целиком плюс листинг каталога назначения с датами. Не расписание — именно файлы.
- Список того, что ходит на сервер по SMTP и IMAP помимо людей: 1С, CRM, сканеры, сайт.
Последний пункт пропускают всегда. Он же потом стоит дороже всех.
И честно про сложность. Команды выше несложные, любой человек с руками их выполнит. Тяжело не собрать вывод — тяжело правильно прочитать. Понять, что 8.8.15 без патч-уровня в 2021 году это одно, а в 2026-м совсем другое. Увидеть, что secondary-тома нет и рост упрётся в один диск. Заметить, что интеграция ходит под админским аккаунтом. Каждая из этих штук отдельно выглядит безобидно, и опасной становится связка.
Если у вас на руках сервер, который никто не описывал: выполните пять команд из блока самодиагностики и пришлите мне вывод. За день отвечу, что у вас на самом деле, где горит и во что обойдётся привести это в порядок. Без выезда и без обследования на месяц.
Частые вопросы
Можно ли сбросить пароль администратора Zimbra, если нет доступа в консоль?
Да, если у вас есть root по SSH. Заходите на сервер, переключаетесь на пользователя zimbra командой su - zimbra и выполняете zmprov sp admin@вашдомен НовыйПароль. Службы останавливать не нужно, перезагрузка не требуется, пользователи ничего не заметят. Единственное, что я советую сделать до смены, — проверить по mailbox.log, не ходит ли под этим ящиком какая-нибудь интеграция. У меня в Подольске под админской учёткой логинился обменник с 1С, и я его уронил на выходные. Двести с лишним заявок пролежали непрочитанными до понедельника.
Как узнать, есть ли на сервере рабочий бэкап, если документации нет?
Смотреть не на расписание, а на файлы. Откройте crontab пользователя zimbra и root, найдите скрипт, откройте его и определите каталог назначения. Затем сделайте ls -la этого каталога и посмотрите даты и размеры. Если самый свежий архив старше недели или каталога вообще нет — бэкапа у вас нет, независимо от того, что написано в комментарии к строке крона. Отдельно проверьте mount: сетевая шара часто отваливается, а скрипт продолжает падать в лог, который никто не читает. Именно так я потерял полдня спокойствия у гостиницы в Подольске.
Пароли пользователей можно как-то вытащить, чтобы не менять их всем при переезде?
Сами пароли — нет, они хранятся хэшами. А вот хэши переносятся: zmprov -l ga пользователь userPassword отдаёт значение атрибута, которое потом заливается на новый сервер той же командой на запись. Это работает при переезде между серверами Zimbra и при миграции на Carbonio, потому что каталог там устроен так же. При переходе на платформы с другим форматом хранения хэшей номер не пройдёт — там придётся либо генерировать новые пароли, либо заводить единый вход через внешний каталог.
Сколько времени занимает полная опись незнакомого почтового сервера?
У меня уходит от шести до двенадцати часов на сервер уровня малого бизнеса — до сотни ящиков, одна машина, все роли на ней. В Подольске получилось девять часов на два дня. Основное время тратится не на команды, а на сверку: кто владелец каждого ящика, что за скрипты висят в кроне, куда ходит интеграция, живы ли сертификаты. Мультисерверная установка с отдельным LDAP и прокси растягивается на два-три дня, потому что там надо описывать связи между узлами, а не только сами узлы.
Насколько опасно, что пароль LDAP лежит на сервере открытым текстом?
Ровно настолько, насколько защищена учётка zimbra на этой машине. Команда zmlocalconfig -s zimbra_ldap_password отдаёт пароль любому, кто получил шелл под этим пользователем, — это архитектурная особенность, а не дыра. Практический вывод такой: всё, что даёт злоумышленнику выполнение команд от zimbra, автоматически отдаёт ему весь каталог с учётками и хэшами. Поэтому патч-уровень сервера и то, что торчит наружу, важнее любых внутренних разграничений прав.
Оставить комментарий