Alex 'AdUser' Z
9 years ago
3 changed files with 239 additions and 3 deletions
@ -0,0 +1,237 @@ |
|||||||
|
--- |
||||||
|
title: Почтовый шлюз: задание со звёздочкой (Усиливаем защиту внешними средствами) |
||||||
|
tags: mail, spam, exim |
||||||
|
--- |
||||||
|
|
||||||
|
Под "внешними средствами" я понимаю дополнительный софт, без которого впринципе можно и обойтись. |
||||||
|
Однако, если его применение облегчит нам жизнь, снизит количества спама или замедлит рост энтропии - почему бы и нет. |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
Отстрел ботов |
||||||
|
------------- |
||||||
|
|
||||||
|
Сейчас практически must-have для почтового сервера. Резко снижает содомию в логах, и загрузку всей остальной системы за счёт меньшей работы антимпама/антивируса/dns'а и т.д. |
||||||
|
|
||||||
|
Для этой задачи вы можете использовать или "старый добрый" fail2ban или мою разработку -- [f2b](http://linuxdv.org/articles/2016/03/17/f2b/)[^fn1]. |
||||||
|
|
||||||
|
Настройку fail2ban вы и без меня осилите, т.к. оно достаточно индивидуально, привожу только failregex. |
||||||
|
Всё что ниже 5й линии - это сообщения из соответствующих проверок ``acl_smtp_helo`` и ``acl_smtp_rcpt``. |
||||||
|
|
||||||
|
failregex = SMTP protocol synchronization error \(input sent without waiting for greeting\): rejected connection from .*\[<HOST>\] |
||||||
|
SMTP protocol synchronization error \(next input sent too soon: pipelining was not advertised\): rejected .*\[<HOST>\] |
||||||
|
rejected EHLO from \[<HOST>\]: syntactically invalid argument |
||||||
|
rejected HELO from \[<HOST>\]: syntactically invalid argument |
||||||
|
Connection from \[<HOST>\] refused: too many connections from that IP address |
||||||
|
\[<HOST>\] .* host is listed in zen.spamhaus.org |
||||||
|
\[<HOST>\] .* host is listed in bl.spamcop.net |
||||||
|
\[<HOST>\] .* Bad rev hostname \(.*\) |
||||||
|
\[<HOST>\] .* relay not permitted |
||||||
|
\[<HOST>\] .* too many connections from that IP address |
||||||
|
\[<HOST>\] .* IP address in HELO |
||||||
|
\[<HOST>\] .* HELO should be FQDN |
||||||
|
\[<HOST>\] .* localhost is a silly HELO |
||||||
|
\[<HOST>\] .* Using my HELO is a bad idea |
||||||
|
\[<HOST>\] .* SPF for sender domain not allows mail from your host |
||||||
|
\[<HOST>\] .* You are blocked |
||||||
|
\[<HOST>\] .* Dont like your hostname |
||||||
|
|
||||||
|
Обратите внимание - используются не все проверки. Логика отбора такая: под раздачу должны попадать хосты, у которых есть какой-то серьёзный косяк, и которые продолжают настойчиво к нам ломиться. |
||||||
|
|
||||||
|
[exim] |
||||||
|
findtime = 300 # если в последние T секунд ... |
||||||
|
maxretry = 5 # .. было N срабатываний для одного и того же ip .. |
||||||
|
bantime = 3600 # .. отправить его отдохнуть на часик |
||||||
|
logpath = /var/log/exim/mainlog |
||||||
|
enabled = true |
||||||
|
filter = exim-custom |
||||||
|
|
||||||
|
f2b настраивается примерно так (только описание самого jail'а): |
||||||
|
|
||||||
|
[jail:postfix] |
||||||
|
enabled = yes |
||||||
|
source = files:/var/log/mail.log |
||||||
|
filter = preg:/etc/f2b/filters/postfix.preg |
||||||
|
backend = exec-ipset:banned |
||||||
|
bantime = 3600 |
||||||
|
|
||||||
|
Грейлистинг |
||||||
|
----------- |
||||||
|
|
||||||
|
В приведённом ранее конфиге были места, где устанавливался флаг, что этот хост нужно проверить грейлистингом, однако самой проверки не было. |
||||||
|
|
||||||
|
Для реализации грейлистинга используется sqlgrey. Спроектирован он изначально под postfix, и его ``check_policy``, здесь это придётся костылять средствами exim'а. |
||||||
|
|
||||||
|
В acl ``check_rcpt``, поближе к концу, но перед ``accept domains = +our_domains``: |
||||||
|
|
||||||
|
# greylisting |
||||||
|
defer message = Service temporarily unavailable, try again later |
||||||
|
condition = ${if eq{$acl_m_greylist}{1} {yes}{no}} |
||||||
|
set acl_m0 = request=smtpd_access_policy\nprotocol_state=RCPT\nprotocol_name=${uc:$received_protocol}\nhelo_name=$sender_helo_name\nclient_address=$sender_host_address\nclient_name=$sender_host_name\nsender=$sender_address\nrecipient=$local_part@$domain\ninstance=$sender_host_address/$sender_address/$local_part@$domain\n\n |
||||||
|
set acl_m0 = ${sg{${readsocket{/var/run/sqlgrey.sock}{$acl_m0}{5s}{}{action=DUNNO}}}{action=}{}} |
||||||
|
condition = ${if eq{${uc:${substr{0}{5}{$acl_m0}}}}{DEFER} {yes}{no}} |
||||||
|
|
||||||
|
Выглядит оно достаточно страшно, но тем не менее работает. |
||||||
|
|
||||||
|
Обратите внимание на используемые переменные вида ``acl_m*`` -- они раскрываются для каждого сообщения, поскольку грейлистинг использует триплеты senderip-from-rcpt. |
||||||
|
Это также означает, что мы можем использовать эту проверку только в пределах ``acl_smtp_rcpt``. |
||||||
|
|
||||||
|
Справочно, конфиг самого sqlgrey: |
||||||
|
|
||||||
|
unix = /var/run/sqlgrey.sock |
||||||
|
reconnect_delay = 10 |
||||||
|
max_connect_age = 24 |
||||||
|
awl_age = 32 |
||||||
|
group_domain_level = 10 |
||||||
|
db_type = SQLite |
||||||
|
db_name = /var/db/sqlgrey/stats.db |
||||||
|
optmethod = optout |
||||||
|
|
||||||
|
DKIM |
||||||
|
---- |
||||||
|
|
||||||
|
В exim'е есть базовая поддержка dkim'а, надо только чтобы она была включена при компиляции. |
||||||
|
|
||||||
|
Этот блок - дописывается в начало файла exim'а: |
||||||
|
|
||||||
|
DKIM_DOMAIN = ${lc:${domain:$h_from:}} |
||||||
|
DKIM_FILE = /etc/exim/${lc:${domain:$h_from:}}.key |
||||||
|
DKIM_PRIVATE_KEY = ${if exists{DKIM_FILE}{DKIM_FILE}{0}} |
||||||
|
|
||||||
|
...и добавляем три строчки к транспорту ``remote_smtp``: |
||||||
|
|
||||||
|
remote_smtp: |
||||||
|
driver = smtp |
||||||
|
interface = 4.3.2.1 |
||||||
|
no_delay_after_cutoff |
||||||
|
# ага, вот эти ребята |
||||||
|
dkim_domain = DKIM_DOMAIN |
||||||
|
dkim_selector = dkim |
||||||
|
dkim_private_key = DKIM_PRIVATE_KEY |
||||||
|
|
||||||
|
Антиспам |
||||||
|
-------- |
||||||
|
|
||||||
|
В нашем варианте в качестве антиспама используется rspamd. |
||||||
|
spamassassin прикручивается почти так же, надо поменять ``variant=`` |
||||||
|
и подправить проверку переменной ``$spam_action`` |
||||||
|
|
||||||
|
Про донастройку rspamd я отдельно расскажу, здесь только как его подключить к exim'у. |
||||||
|
|
||||||
|
Вот этот блок - в начало конфига. |
||||||
|
|
||||||
|
spamd_address = 127.0.0.1 11333 variant=rspamd |
||||||
|
|
||||||
|
А этот - дописать в acl ``check_data``, чтобы он принял примерно такой вид: |
||||||
|
|
||||||
|
check_data: |
||||||
|
# не проверять сообщения с наших собственных серверов |
||||||
|
accept hosts = +our_networks : +our_hosts : +relay_from_hosts |
||||||
|
|
||||||
|
# пропускать всё с "наших" доменов |
||||||
|
# с проверкой, что письмо от "нашего" домена идёт с "наших" хостов - |
||||||
|
# надо было разбираться раньше |
||||||
|
accept sender_domains = +our_domains |
||||||
|
|
||||||
|
# не проверять сообщения, если используется submission |
||||||
|
accept condition = ${if eq {$interface_port}{587} {yes}{no}} |
||||||
|
|
||||||
|
# не проверять юзеров, которые авторизовались в системе |
||||||
|
accept authenticated = * |
||||||
|
|
||||||
|
# это правило как раз и отправляет письмо на проверку антиспамом |
||||||
|
# nobody - это "профиль пользователя" в антиспаме |
||||||
|
warn spam = nobody:true |
||||||
|
|
||||||
|
# тут - проверка антивирусником, смотри далее |
||||||
|
|
||||||
|
# если письмо набрало больше порога - отлуп |
||||||
|
deny condition = ${if eq{$spam_action}{reject}} |
||||||
|
message = Message discarded as high-probability spam |
||||||
|
|
||||||
|
# если антиспам считает письмо "хорошим" - добавить хидер |
||||||
|
# потом это поможет в разбирательствах, попало письмо в проверку или нет |
||||||
|
accept condition = ${if eq{$spam_action}{no action} {yes}{no}} |
||||||
|
add_header = X-Spam-Status: No |
||||||
|
|
||||||
|
# если письмо набрало сколько-то баллов, но недобрало до отлупа - |
||||||
|
# добавляем дополнительную информаию для дальнейших разбирательств |
||||||
|
# |
||||||
|
# я дальше покажу как отловить такие письма и сложить отдельно |
||||||
|
accept condition = ${if eq{$spam_action}{add header} {yes}{no}} |
||||||
|
add_header = X-Spam-Status: Yes |
||||||
|
add_header = X-Spam-Score: $spam_score ($spam_bar) |
||||||
|
add_header = X-Spam-Report: $spam_report |
||||||
|
|
||||||
|
# ну ладно, так уж и быть - пущу |
||||||
|
accept |
||||||
|
|
||||||
|
Антивирус |
||||||
|
--------- |
||||||
|
|
||||||
|
Антивирусов под линуксом немного, запутаться сложно. |
||||||
|
По дефолту показываю на примере clamav, если у вас что-то другое - исправляйте под свои реалии. |
||||||
|
|
||||||
|
В начало конфига, к общим настройкам: |
||||||
|
|
||||||
|
av_scanner = clamd:/var/run/clamav/clamd.sock |
||||||
|
|
||||||
|
В секцию acl'ов, дописать в середину ``check_data``: |
||||||
|
|
||||||
|
check_data: |
||||||
|
<...> |
||||||
|
# discard viruses and malware |
||||||
|
deny malware = */defer_ok |
||||||
|
message = This message was detected as possible malware |
||||||
|
|
||||||
|
``defer_ok`` - говорит серверу при недоступности антивирусника или его кривой настройкой, |
||||||
|
отвечать 4XX ошибками - "сервис временно недоступен" (вместо 5XX). Почта не пропадёт. |
||||||
|
|
||||||
|
Немного про эффективность clamd: официальные базы ловят немного, и обновляются весьма неспешно, |
||||||
|
по моим наблюдениям - примерно пятую часть от новья и половину от старых. |
||||||
|
Разгребая спецящик "под спам" в день находишь 2-3 бинарника/архива. Смотришь логи clamav, но уже за неделю - те же 2-3 шт. |
||||||
|
Хотя надо учитывать ещё две вещи - наличие DNSBL/XBL (думаю, без них до clamav доходило бы намного больше) и сам характер непойманных "вирусов". |
||||||
|
|
||||||
|
Примеры из непойманного: |
||||||
|
|
||||||
|
* zip-архив, внутри - js-скрипт с eval'ом. Не знаю на кого это рассчитано и как оно намеревалось запускаться. |
||||||
|
* запароленные архивы, пароль указан в самом письме. Часть из них clamav всё же ловит, но не всё. |
||||||
|
* rar, переименованный в маргинальщину типа .ace. Это рассчитано на winrar, который жрёт всё. Такое clamav должен ловить. |
||||||
|
|
||||||
|
Чтобы победить эту напасть и повысить уловы, рекомендуют заюзать неофициальные базы: [тыц](https://github.com/extremeshok/clamav-unofficial-sigs) |
||||||
|
Часть из них платные, часть - полностью свободные. При подключении части этих баз, отсекается порядка 80% случаев, типа описанных выше. |
||||||
|
|
||||||
|
Потребление памяти в процессе работы (до 20 писем/мин): ~350 метров. |
||||||
|
|
||||||
|
Если есть желание пополнить базу, вот скриптик в помощь: |
||||||
|
|
||||||
|
#!/bin/sh |
||||||
|
# исправьте на свои |
||||||
|
USER="Alex Z" |
||||||
|
EMAIL="ad_user@runbox.com" |
||||||
|
# нет прокси - убрать или закомментировать |
||||||
|
PROXY="http://192.168.49.1:8080" |
||||||
|
export http_proxy="$PROXY" |
||||||
|
export https_proxy="$PROXY" |
||||||
|
|
||||||
|
ls *.zip 2> /dev/null | while read ZIP; do |
||||||
|
unzip "$ZIP" \ |
||||||
|
&& rm -vf "$ZIP" |
||||||
|
done |
||||||
|
|
||||||
|
ls *.exe *.ace *.scr *.jar *.doc *.vbs 2> /dev/null | while read FILE; do |
||||||
|
echo "Processing $FILE" |
||||||
|
clamsubmit -N "$USER" -e "$EMAIL" -n "$FILE" > /dev/null \ |
||||||
|
&& rm -vf "$FILE" |
||||||
|
done |
||||||
|
|
||||||
|
Выделяем где-нибудь отдельную директорию, кладём туда этот скриптик, накидываем "улов" и запускаем. |
||||||
|
Занимает времени - полминуты, но повышает ЧСВ на 100500. |
||||||
|
|
||||||
|
Опять же по опыту, добавляют в базу примерно 1/30 образцов. |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
[К оглавлению](/articles/2016/04/10/hardcore-mail-relay-1/) |
||||||
|
|
||||||
|
[^fn1]: Себя не попиаришь - так и помрёшь в безвестности. |
Loading…
Reference in new issue