Почтовый шлюз: задание со звёздочкой (Усиливаем защиту внешними средствами)

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

Отстрел ботов

Сейчас практически must-have для почтового сервера. Резко снижает содомию в логах, и загрузку всей остальной системы за счёт меньшей работы антимпама/антивируса/dns'а и т.д.

Для этой задачи вы можете использовать или "старый добрый" fail2ban или мою разработку -- f2b.

Настройку 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 должен ловить.

Чтобы победить эту напасть и повысить уловы, рекомендуют заюзать неофициальные базы: тыц Часть из них платные, часть - полностью свободные. При подключении части этих баз, отсекается порядка 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 образцов.

К оглавлению, Далее: Обратная связь и переобучение системы