Как в mailcow проверять EXE внутри ZIP, если правило Sieve по расширению вложения не срабатывает
Sieve-правило, которое смотрит на имя вложения, видит только archive.zip — и пропускает архив с исполняемым файлом внутри, потому что физически не умеет заглядывать внутрь ZIP. В mailcow за это отвечает не Sieve, а модуль mime_types в Rspamd — показываю, как он устроен, какие символы означают опасное вложение и что реально настраивается, а что пока остаётся ограничением.
Кейс: «Промо Максимум» и архив, который прошёл фильтр
«Промо Максимум» — рекламное агентство, 17 рабочих мест, почта на mailcow, которую мы сопровождаем. У них был Sieve-фильтр в global_sieve_before, написанный ещё до нас, — правило проверяло заголовок вложений по regex на предмет расширений exe, com, cmd, scr в имени файла и должно было блокировать такие письма на входе.
Правило исправно ловило письма, где вредоносный файл был приложен напрямую с расширением .exe. Но однажды через фильтр прошло письмо с архивом invoice.zip, внутри которого лежал invoice.exe — сам ZIP правилу не показался подозрительным, потому что его имя не содержало ни одного из проверяемых расширений, а заглянуть внутрь архива правило Sieve физически не способно.
Сотрудник, получивший письмо, распознал подделку по контексту (незнакомый отправитель, странный текст) и не стал открывать вложение, но случай ясно показал: фильтрация по имени файла в заголовке — это фильтр по обёртке, а не по содержимому, и рассчитывать на неё как на защиту от архивов с вредоносным содержимым нельзя в принципе.
Похожая история уже случалась в почтовой инфраструктуре mailcow на более серьёзном уровне — уязвимость в шаблонах CVE-2025-53909 тоже была про разрыв между тем, что администратор считал защищённым по умолчанию, и тем, что реально проверялось под капотом. Для «Промо Максимум» это стало дополнительным поводом не откладывать разбор с вложениями на потом.
Почему Sieve не видит вложенное содержимое ZIP
Sieve — язык фильтрации на уровне структуры письма: он умеет проверять заголовки, MIME-части верхнего уровня, их имена и заявленные типы через тесты вроде header, address или расширение mime для проверки параметров конкретной MIME-части. Но у Sieve нет встроенного механизма распаковки архивов и анализа их содержимого — это принципиальное архитектурное ограничение языка, а не недоработка конкретной реализации в mailcow.
# так выглядит правило «по имени файла в заголовке» — и его ограничение
if header :contains "Content-Disposition" ".exe" {
discard;
}
# invoice.zip с invoice.exe внутри это правило не поймаетПоэтому в русскоязычном обсуждении на linux.org.ru, где автор пытался закрыть эту дыру именно через Sieve и заголовок X-Attached, устойчиво работающего решения в итоге не получилось — задача принципиально не решается на уровне Sieve, потому что нужный ей анализ происходит раньше, на этапе разбора письма спам-фильтром, а не на этапе доставки в ящик.
Это тот же принцип, что и в серверной фильтрации почты через Sieve в целом: Sieve прекрасно решает задачи маршрутизации и сортировки уже принятого и разобранного письма, но не задачи глубокого анализа содержимого — для этого в цепочке обработки почты есть отдельный, специализированный компонент, и в mailcow это Rspamd, а не Dovecot с его Sieve-движком.
Кто реально это проверяет: модуль mime_types в Rspamd
В mailcow за анализ вложений, включая содержимое архивов, отвечает Rspamd — конкретно модуль mime_types. Согласно официальной документации Rspamd, начиная с версии 1.3 этот модуль поддерживает обработку архивов в форматах ZIP и RAR: он не просто смотрит на расширение файла в имени вложения, а читает список файлов внутри архива и проверяет их расширения. Важная оговорка: проверяются именно имена. Тип содержимого (content-type, сигнатуры libmagic) для файлов внутри архива не определяется — разработчик Rspamd прямо писал в issue #2998, что распаковку для этого добавлять не планируют. EXE, переименованный внутри ZIP в invoice.pdf, этим механизмом не ловится.
Модуль формирует несколько разных символов (symbols) в зависимости от того, что именно найдено: MIME_BAD_EXTENSION — файл с расширением из списка опасных (базовый вес 2.0, умножается на коэффициент расширения); MIME_BAD_ATTACHMENT — расхождение между расширением и заявленным MIME-типом вложения (вес 4.0); MIME_DOUBLE_BAD_EXTENSION — двойное расширение вида invoice.pdf.exe; и архивные символы MIME_ARCHIVE_IN_ARCHIVE (архив внутри архива, вес 5.0) и MIME_ENCRYPTED_ARCHIVE (зашифрованный или нечитаемый архив, вес 2.0). Деталь, о которой мало кто знает: в mailcow вес MIME_DOUBLE_BAD_EXTENSION обнулён в data/conf/rspamd/local.d/mime_types_group.conf (weight = 0), так что сам по себе этот символ на итог не влияет.
Эти символы не блокируют письмо сами по себе автоматически — как и большинство правил Rspamd, они добавляют вес к общей оценке письма (score), и уже итоговый score, пройдя через пороги действий — в mailcow по умолчанию в data/conf/rspamd/local.d/actions.conf это greylist = 7, add_header = 8, reject = 15, определяет, что произойдёт с письмом дальше. Это отличается от Sieve, где правило либо срабатывает и сразу что-то делает с письмом, либо нет.
Практическое следствие для администратора: одного факта, что письмо содержит EXE в архиве, обычно недостаточно, чтобы гарантированно получить reject — если остальная часть письма выглядит легитимно (правильный SPF/DKIM, знакомый домен отправителя), итоговый score может остаться ниже порога отклонения. Именно поэтому для действительно опасных расширений стоит не полагаться на дефолтные веса, а поднимать множитель вручную под свою модель угроз.
Где настраивается и что можно изменить
В mailcow конфигурация mime_types переопределяется в data/conf/rspamd/local.d/mime_types.conf — стандартный для Rspamd способ локально дополнить или переопределить базовые правила модуля, не трогая исходные файлы внутри контейнера.
# data/conf/rspamd/local.d/mime_types.conf — добавить в секцию bad_archive_extensions
# (значения мержатся с таблицами модуля; число — множитель к весу MIME_BAD_EXTENSION)
bad_archive_extensions = {
exe = 20,
scr = 20,
com = 20,
js = 8,
# строки jar, jnlp, bat, cmd, pdf, docx и др., уже стоящие в файле mailcow, оставить
};
# применить
docker compose restart rspamd-mailcowГлавная ловушка — в том, как модуль выбирает множитель. В файле две таблицы: bad_extensions (для обычных вложений; в mailcow там exe = 20, scr = 20, com = 20) и bad_archive_extensions (для файлов внутри архива). Для архива с одним-единственным файлом модуль сначала смотрит в bad_archive_extensions, а там у самого Rspamd для exe стоит 0.1 — mailcow эту строку не переопределяет. Итог: invoice.exe, приложенный напрямую, получает 2.0 × 20 = 40 баллов, а тот же invoice.exe в одиночном invoice.zip — 2.0 × 0.1 = 0,2 балла. Если в архиве несколько файлов, модуль берёт множитель уже из bad_extensions. Здесь же задаётся список archive_exceptions — форматы, которые физически являются ZIP-контейнерами, но не должны разбираться как архивы: docx, xlsx, pptx, odt, ods, odp, vsdx. Без него каждое письмо с обычным Word- или Excel-файлом давало бы ложные срабатывания.
Проверить итоговую оценку конкретного письма проще всего через историю Rspamd в веб-интерфейсе mailcow: там видно, какие именно symbols сработали и с каким весом, — это быстрее, чем гадать по логам, почему письмо с архивом получило тот или иной score.
Ещё один практический момент, который легко упустить: зашифрованные архивы (пароль на ZIP) модуль пометит символом MIME_ENCRYPTED_ARCHIVE именно потому, что не может заглянуть внутрь и проверить содержимое — с точки зрения защиты это фактически «слепая зона», и наличие пароля само по себе не делает вложение безопаснее, а как раз наоборот, чаще встречается в целенаправленных атаках именно как способ обойти автоматическую проверку содержимого.
Жёсткий отказ с понятной причиной и честные ограничения
На GitHub у Rspamd есть issue #2998 «Reject extensions from mime type or inside archive» — запрос 2019 года на отказ по вложению в архиве с отдельным SMTP-сообщением, а не общей спам-оценкой. Его закрыли в августе того же года: после исправления модуль multimap с типом filename стал смотреть и имена файлов внутри архивов. Сейчас это прямо написано в документации multimap: тип filename проверяет имена вложений, включая файлы в архивах, а отключается это параметром skip_archives = true.
# data/conf/rspamd/local.d/multimap.conf — дописать правило в конец файла
BAD_EXT_ANYWHERE {
type = "filename";
filter = "extension";
map = "${LOCAL_CONFDIR}/custom/bad_ext.map";
action = "reject";
message = "Attachment type is not allowed";
}
# data/conf/rspamd/custom/bad_ext.map — по расширению на строку: exe, scr, comТакое правило срабатывает как префильтр: письмо отклоняется на этапе SMTP с вашим текстом, независимо от общего score, — ровно то, чего хотели авторы issue. Оговорки те же, что у mime_types: проверяются имена, а не содержимое, поэтому переименованный внутри архива файл не поймается; зашифрованный архив Rspamd пометит MIME_ENCRYPTED_ARCHIVE, но что внутри, по-настоящему не узнает. Для проверки содержимого нужен антивирус — в mailcow это ClamAV (контейнер clamd-mailcow, если он не отключён через SKIP_CLAMD=y в mailcow.conf), который распаковывает архивы сам.
Жёсткий reject я ставлю только на короткий список, где у компании точно нет рабочих сценариев: exe, scr, com. Остальное — через повышенные множители в mime_types, чтобы бухгалтерия не потеряла письмо с легитимным .js-скриптом от разработчиков сайта. Учтите и то, что файлы local.d/multimap.conf и local.d/mime_types.conf принадлежат репозиторию mailcow: update.sh сохраняет локальные правки, но после каждого обновления я проверяю, что правило на месте.
Что мы сделали в «Промо Максимум»
Старое Sieve-правило по расширению в заголовке мы не стали трогать — оно продолжает ловить прямые вложения .exe/.com/.cmd/.scr, это дешёвая и рабочая первая линия, которая не мешает работе mime_types и не дублирует её впустую. Отдельно проверили, что модуль mime_types в Rspamd включён и архивы разбирает (поддержка появилась в 1.3, в текущих сборках mailcow — ветка Rspamd 4.x). Включён — но в истории Rspamd то самое письмо с invoice.zip получило от MIME_BAD_EXTENSION всего 0,2 балла: сработал множитель exe = 0.1 для одиночного файла в архиве.
Что мы поправили — добавили exe, scr, com = 20 и js = 8 в секцию bad_archive_extensions файла data/conf/rspamd/local.d/mime_types.conf, чтобы одиночный исполняемый файл в архиве весил столько же, сколько приложенный напрямую, и сразу переходил порог reject = 15. Поверх этого завели правило multimap с action = "reject" и понятным текстом отказа для exe, scr и com — отправитель получает внятную причину, а не абстрактный спам-отказ. Руководителю агентства отдельно показали историю Rspamd на тестовых письмах — чтобы у него было наглядное подтверждение, что деньги на настройку ушли не в абстрактную «безопасность», а в конкретно проверяемый результат.
После изменений мы прогнали тестовые письма с EXE внутри ZIP, RAR и вложенным архивом внутри архива — все три сценария были отклонены правилом multimap, а в истории Rspamd видны ожидаемые символы mime_types, включая MIME_ARCHIVE_IN_ARCHIVE для вложенного архива. Это заняло меньше часа вместе с проверкой, но закрыло дыру, которую старое Sieve-правило в принципе не могло закрыть — независимо от того, сколько расширений в него добавить.
Эту проверку — что письмо с опасным вложением в архиве реально получает нужный score и попадает под порог действия — мы теперь включаем отдельным пунктом в регламент эксплуатации mailcow, по которому обслуживаем клиентские серверы: делаем её не только сразу после настройки, но и повторяем после каждого крупного обновления Rspamd, потому что веса и пороги в новых версиях модуля иногда меняются.
Частые вопросы
Почему Sieve-правило по расширению не ловит EXE внутри ZIP?
Sieve проверяет заголовки и MIME-части письма на верхнем уровне, но не умеет распаковывать архивы и анализировать их содержимое — это архитектурное ограничение языка, а не недоработка mailcow. Проверка содержимого архивов происходит раньше, на этапе разбора письма спам-фильтром Rspamd.
Какой компонент в mailcow реально проверяет содержимое ZIP-вложений?
Модуль mime_types в Rspamd, а для жёсткого отказа — multimap с типом filename. Оба читают имена файлов внутри ZIP и RAR (mime_types — с версии 1.3), но не их содержимое: переименованный внутри архива EXE ловит только антивирус (ClamAV в mailcow).
Что означают символы MIME_BAD_EXTENSION и MIME_BAD_ATTACHMENT?
MIME_BAD_EXTENSION — у вложения или файла в архиве опасное расширение; базовый вес 2.0 умножается на коэффициент расширения. MIME_BAD_ATTACHMENT — расширение не совпадает с заявленным MIME-типом (вес 4.0). Оба добавляют баллы к score, а не блокируют письмо напрямую.
Почему офисные документы docx/xlsx не считаются подозрительными архивами?
Формально это ZIP-контейнеры, но в конфигурации mime_types есть опция archive_exceptions, которая исключает такие форматы (docx, xlsx, pptx, odt, ods, odp, vsdx) из проверки содержимого как обычного архива — иначе каждое письмо с Word-файлом давало бы ложное срабатывание.
Почему EXE в ZIP получил почти ноль баллов, хотя exe = 20 в mime_types.conf mailcow?
Для архива с одним файлом Rspamd берёт множитель из bad_archive_extensions, где у exe по умолчанию 0.1, а mailcow эту строку не переопределяет: 2.0 × 0.1 = 0,2 балла. Добавьте exe = 20 в bad_archive_extensions в data/conf/rspamd/local.d/mime_types.conf.
Можно ли настроить mailcow так, чтобы письмо с EXE в архиве отклонялось с явным объяснением причины?
Да. Правило multimap с type = "filename", filter = "extension", action = "reject" и параметром message отклоняет письмо на этапе SMTP с вашим текстом; имена файлов внутри архивов этот тип проверяет по умолчанию. Запрос об этом, issue #2998 в Rspamd, закрыт ещё в 2019 году.
Источники
- Rspamd docs: Mime types module — Поддержка архивов ZIP/RAR с версии 1.3, symbols MIME_BAD_EXTENSION, MIME_BAD_ATTACHMENT, MIME_DOUBLE_BAD_EXTENSION, MIME_ARCHIVE_IN_ARCHIVE, MIME_ENCRYPTED_ARCHIVE, опция archive_exceptions с перечнем офисных форматов. https://docs.rspamd.com/modules/mime_types/
- GitHub rspamd: Issue #2998 — Reject extensions from mime type or inside archive — Запрос 2019 года на reject по вложению в архиве с отдельным сообщением; закрыт 21.08.2019 после коммита, научившего multimap (type filename) смотреть имена в архивах; комментарий разработчика: content-type/libmagic для файлов в архиве не проверяются. https://github.com/rspamd/rspamd/issues/2998
- mailcow community: rspamd bad_extensions mod — Обсуждение конфигурации local.d/mime_types.conf и множителей score для расширений в контексте mailcow. https://community.mailcow.email/d/888-rspamd-bad-extensions-mod
- linux.org.ru: фильтр для Sieve, режущий опасные вложения — Практическая попытка закрыть задачу через global_sieve_before и заголовок X-Attached — подтверждает отсутствие устойчивого решения именно на уровне Sieve. https://www.linux.org.ru/forum/admin/17174870
- Rspamd docs: Multimap module — Тип filename проверяет имена в архивах (skip_archives = true отключает), filter = "extension", action = "reject" и message для префильтра. https://docs.rspamd.com/modules/multimap/
- mailcow-dockerized: data/conf/rspamd/local.d — mime_types.conf (bad_extensions exe = 20, bad_archive_extensions без exe), mime_types_group.conf (MIME_DOUBLE_BAD_EXTENSION weight = 0), actions.conf (greylist 7 / add_header 8 / reject 15). https://github.com/mailcow/mailcow-dockerized/tree/master/data/conf/rspamd/local.d



