Мы — долго запрягаем, быстро ездим, и сильно тормозим.
www.lissyara.su —> статьи —> FreeBSD —> почтовые системы —> Защита от DDoS

Попытка защитить свой почтовый сервер от DDoS атаки.

Автор: buryanov.


Друг попросил о помощи, его почтовик подвергся ddos атаке. У меня были написаны скрипты, но у меня не настолько большая нагрузка, чтобы можно было их потестить по нормальному, да и времени тоже не было. Навеяло на их написание, точнее дописание обсуждение Защита exim от DDoS. А тут как раз и повод появился.

В моём случае почтовый сервер и почтовый релей - это два разных сервера, но, если это один и тот же сервер - то всё работает также прекрасно, что и было доказано.

Топология:
- пограничный маршрутизатор: FreeBSD 7.2/i386, Atom c 1 сетевой картой, зато с кучей vlan
- почтовик, он же и прокся, он же и MySQL сервер: FreeBSD 7.2/amd64, Core2Duo E8500, 8G Ram

Теперь по порядку:
8 - 10 тысяч одновременных конектов к exim, при условии, что в офисе работает 50 человек. Это уже слишком. Попахивает DDos'ом.
Ддосили smtp, imap не юзается, точнее юзается imaps, а его, почему-то, не ддосят.

Перед какими либо действиями обновляем порты, я это делаю с помощью cvsup.
[root@mx ]# cd /usr/ports/net/cvsup-without-gui
[root@mx ]# make install clean

Создаём к ниму конфиг-файл /usr/local/etc/cvsup/ports-sup
*default host=cvsup3.uk.freebsd.org
*default base=/var/db
*default prefix=/usr
*default release=cvs tag=.
*default delete use-rel-suffix
*default compress
ports-all

После чего командой
cvsup -g -L 2 /usr/local/etc/cvsup/ports-sup

обновляем порты.

Ставим Mysql:

[root@mx /usr/ports]# cd /usr/ports/databases/mysql51-server/
[root@mx ]# make install clean
[root@mx ]# echo 'mysql_enable="YES"' >> /etc/rc.conf

В конфиге указывываем побольше количество коннектов, параметр max_connections=1000 в секции [mysqld]. В моём случае это было 10000. По умолчанию файл конфигурации my.cnf будет находится в каталоге с самой базой /var/db/mysql, но пока мы не инициализируем mysql, там ничего не будет. Это можно сделать несколькими способами, один из них, запустить mysql, он сам всё сделает, затем отредактировать конфигурационный файл и перезапустить mysql. Запуск осуществляется командой
[root@mx /var/db]# /usr/local/etc/rc.d/mysql-server start

Более детально установка MySQL описана в статье Установка MySQL Автор Lissyara.

Ставим exim:
[root@mx /var/db]# cd /usr/ports/mail/exim
[root@mx /usr/ports/mail/exim]# make install clean
[root@mx ]# echo 'exim_enable="YES"' >> /etc/rc.conf

На этом сайте приведено множество примеров конфигурирования exim под разные цели: exim & dovecot, exim + exchange автор Lissyara, мои конфиги базируются на этих статьях, поэтому повторяться не буду, напишу только свои дополнения:
В файле конфигурации /usr/local/etc/exim/configure, если ваш файл разбит на несколько файлов, то в соответвтвующих файлах, параметр
# Максимальное число одновременных подключений по
# SMTP. Рассчитывать надо исходя из нагрузки на сервер
smtp_accept_max = 50

необходимо сделать побольше, иначе ваш exim буквально сразу захлебнётся. Перед begin acl создаём макрос:
MYSQL_IPFW     =       INSERT INTO `ipfw` \
(`ip`, `date`, `type`, `coun`,`email`) VALUES \
  ('$sender_host_address', now(), '$acl_m18', '1', '$sender_address')

Далее в разделе acl, создаём переменные:
        warn    set acl_m18     =       0
        warn    set acl_m19     =       0

и добавляем в секции проверок, в которых идут следующие  запреты:
        set acl_m18     = HELO1
        set acl_m19     = ${lookup mysql{MYSQL_IPFW}}

В разделе на проверки на EHLO/HELO, для каждой проверки можно присваивать свои значения $acl_m18, HELO1,HELO2,HELO10, чтобы потом отслеживать , где попался

        set acl_m18     = DNSRBL
        set acl_m19   = ${lookup mysql{MYSQL_IPFW}}

вносим, на проверке на RBL

        set acl_m18     = UnUser
        set acl_m19   = ${lookup mysql{MYSQL_IPFW}}

вносим, за попытку отправить почту несуществующим пользователям.
Создаём таблицы в MySQL, у меня exim использует базу exim_db, если у вас другая, то необходимо исправить на свою:
-- Создаём таблицу для занесения статистики
CREATE TABLE ipfw(
  ip CHAR (15) NOT NULL,
  `date` CHAR (19) NOT NULL,
  type CHAR (9) NOT NULL,
  coun INT (8) NOT NULL,
  email VARCHAR (64) DEFAULT NULL,
  INDEX `date` USING BTREE (`date` (13)),
  INDEX ip USING BTREE (ip),
  INDEX type USING BTREE (type)
)
ENGINE = MYISAM
CHARACTER SET latin1
COLLATE latin1_swedish_ci;

-- Создаём таблицы для анализа и занесения результатов
CREATE TABLE ddos_analis(
  ip CHAR (15),
  type CHAR (9) DEFAULT NULL,
  date_in CHAR (19) DEFAULT NULL,
  date_last CHAR (19) DEFAULT NULL,
  date_exp CHAR (19) DEFAULT NULL,
  coun INT (8) UNSIGNED DEFAULT NULL,
  PRIMARY KEY (ip),
  UNIQUE INDEX ip_type USING BTREE (ip, type)
)
ENGINE = MEMORY
CHARACTER SET koi8r
COLLATE koi8r_general_ci;

CREATE TABLE ddos_exp(
  ip CHAR (15) NOT NULL,
  date_delete CHAR (19) DEFAULT NULL,
  active CHAR (1) DEFAULT '0',
  UNIQUE INDEX ip USING HASH (ip)
)
ENGINE = MEMORY
CHARACTER SET koi8r
COLLATE koi8r_general_ci;

Теперь создаём логику, которая и будет почти всё делать
CREATE
TRIGGER ddos_analis1
AFTER INSERT
ON ipfw
FOR EACH ROW
BEGIN
  SET @coun = new.coun + 1;
  SET @ip = new.ip;
  SET @type = new.type;
  -- Время первого заноса в таблицу
  SET @date_in = new.`date`;
  -- Время последнего заноса в таблицу
  SET @date_last = new.`date`;
  -- Интервал, за который идёт анализ
  SET @date_exp = NOW() + INTERVAL 5 MINUTE;
  -- Интервал, для чего-то создавался, мож быть где-то пригодиться
  -- SET @date_delete = NOW() + INTERVAL 1 DAY;

  -- Заносим данные в таблицу колектор и, в случаи нахождения ip,
  -- обновляем и добавляем очки
  INSERT
  INTO exim_db.ddos_analis (ip, type, date_in, date_last, date_exp, coun)
  VALUES (@ip, @type, @date_in, @date_last, @date_exp, 1)
  ON DUPLICATE KEY
  UPDATE
    exim_db.ddos_analis.coun = exim_db.ddos_analis.coun + 1,
    exim_db.ddos_analis.date_last = @date_last,
    exim_db.ddos_analis.date_exp = @date_exp;
  
  -- Заносим данные в таблицу, из которой информацию черпает фаервол
  -- Данные заносятся из таблицы коллектора
  -- coun >10 - это условие, при котором можно с уверенностью сказать,
  -- что вас или спамят или ддосят, интервал анализа - @date_exp
  -- date_exp + INTERVAL 10 MINUTE интервал, 
  -- сколько в фаерволе будет находится ip
  INSERT IGNORE
  INTO exim_db.ddos_exp (ip, date_delete)
  SELECT
    ip, date_exp + INTERVAL 10 MINUTE
  FROM
    exim_db.ddos_analis
  WHERE
    coun > 10;

  -- Удаляем просроченные записи или записи из таблицы коллектора
  -- по времени оканчания срока жизни @date_exp
  DELETE
  FROM
    exim_db.ddos_analis
  WHERE
    LEFT(date_exp, 16) < NOW();

  -- Удаляем из таблицы коллектора свои подсети
  -- Надо будет переделать на свой whitelist, хранимый в мускуле
  DELETE
  FROM
    exim_db.ddos_analis
  WHERE
    ip LIKE '10.4.%' OR ip LIKE '192.168.%';
END

В триггере присутствуют комментарии, поэтому писать не буду ничего

Создаём скрипт на маршрутизаторе add_ipfw.sh:
#!/bin/sh

/usr/local/bin/mysql --host=ip_you_mysql_server \
--password='xх' --user=user_to_access_to_mysql_server exim_db --execute=' \

update exim_db.ddos_exp \
    set active = 2 \
  WHERE \
    LEFT(date_delete, 16) < NOW() and active = 1; \

UPDATE exim.ddos_exp \
        SET active = 5 where active = 0; \

SELECT "ipfw table 125 add " asdf, ip FROM exim_db.ddos_exp \
    WHERE \
      active = 5; \

SELECT "ipfw table 125 delete " asdf, ip FROM exim_db.ddos_exp \
    WHERE \
      active = 2; \

UPDATE exim_db.ddos_exp \
    SET active = 1 where active = 5; \

delete FROM exim_db.ddos_exp \
  WHERE \
    LEFT(date_delete, 16) < NOW() and active = 2;'| \
grep -v asdf>/tmp/exim_ipfw_add_table_blacklist.sh

. /tmp/exim_ipfw_add_table_blacklist.sh

rm  /tmp/exim_ipfw_add_table_blacklist.sh

Делаем скрипт выполнимым
[root@hqgw1 ]$ chmod +x add_ipfw.sh

Комментарии: в таблице ddos_exp есть поле active, которое отвечает за состояние ip в фаерволе, может принимать 3 значения:
0 - ip ожидают вноса в таблицу фаервола
1 - данные внесены
2 - данные ожидают удаления из фаервола

У меня используется ipfw. В крон root добавляем
* * * * * /путь/к/вашему/скрипту/add_ipfw.sh

В фаер добавляем:
#deny mail
${fwcmd} add 56 deny log ip from table\(125\) to any 25 via ${ifwan}

.
.
.
.
Следующей немаловажной защитой является защита от так называемых пустых коннекшинов, трафик как бы не идёт, но слот занят и потом отваливается с записью в логе
2009-09-07 15:12:42 [40591] SMTP connection from [91.78.180.232]:1666
I=[217.112.209.34]:25 lost
2009-09-07 15:12:42 [40591] no MAIL in SMTP connection from [91.78.180.232]:1666
I=[217.112.209.34]:25 D=4m38s

С помощью конфига Exim, я не нашел способа бороться, но у меня есть замечательный файл /var/log/all.log, в который пишется весь(почти) лог. Создаётся он так: в файле /etc/syslog.conf раскомментируется строка:
*.*     /var/log/all.log

затем
touch /var/log/all.log
/etc/rc.d/syslogd restart

Этот файл у меня и так парсится на предмет перебора паролей на ftp, ssh. Так пусть и exim'у он тоже поможет.

Создаём таблицу для занесения статистики:
CREATE TABLE ddos_nomail(
  `data` VARCHAR (255) DEFAULT NULL
)
ENGINE = MYISAM
CHARACTER SET koi8r
COLLATE koi8r_general_ci;

Создаём логику:
CREATE
TRIGGER trigger_nomail
AFTER INSERT
ON ddos_nomail
FOR EACH ROW
BEGIN

  SET @ip = new.`data`;
  SET @type = "noMAIL";
  -- Время первого заноса в таблицу
  SET @date_in = NOW();
  -- Время последнего заноса в таблицу
  SET @date_last = NOW();
  -- Интервал, за который идёт анализ
  SET @date_exp = NOW() + INTERVAL 5 MINUTE;
  -- Интервал, для чего-то создавался, мож быть где-то пригодиться
  -- SET @date_delete = NOW() + INTERVAL 1 DAY;

  -- Заносим данные в таблицу коллектор и, в случае нахождения ip, 
  -- обновляем и добавляем очки
  INSERT
  INTO exim_db.ddos_analis (ip, type, date_in, date_last, date_exp, coun)
  VALUES (SUBSTRING_INDEX(SUBSTRING_INDEX(@ip, "]:", 2), "[", -1), 
@type, @date_in, @date_last, @date_exp, 1)
  ON DUPLICATE KEY
  UPDATE
    exim_db.ddos_analis.coun = exim_db.ddos_analis.coun + 1,
    exim_db.ddos_analis.date_last = @date_last,
    exim_db.ddos_analis.date_exp = @date_exp;

  -- Заносим данные в таблицу, из которой информацию черпает фаервол
  -- Данные заносятся из таблицы коллектора
  -- coun >10 - это условие, при котором можно с уверенностью сказать,
  -- что вас или спамят или ддосят, интервал анализа - @date_exp
  -- date_exp + INTERVAL 10 MINUTE интервал, 
  -- сколько в фаерволе будет находится ip
  INSERT IGNORE
  INTO exim_db.ddos_exp (ip, date_delete)
  SELECT
    ip, date_exp + INTERVAL 10 MINUTE
  FROM
    exim_db.ddos_analis
  WHERE
    coun > 10;

  -- Удаляем просроченные записи или записи из таблици коллектора
  -- по времени окончания срока жизни @date_exp
  DELETE
  FROM
    exim_db.ddos_analis
  WHERE
    LEFT(date_exp, 16) < NOW();

  -- Удаляем из таблицы коллектора свои подсети
  -- Надо будет переделать на свой whitelist, хранимый в мускуле
  DELETE
  FROM
    exim_db.ddos_analis
  WHERE
    ip LIKE '10.4.%' OR ip LIKE '192.168.%';


END

Создаём очередной скрипт all_log_pars.sh
#!/bin/sh
msql_e="/usr/local/bin/mysql --user=exim_ins --password=х \
--database=exim_db --host=12.12.12.12"
lf="/var/log/all.log"

cp ${lf} ${lf}.tmp
> ${lf}
cat ${lf}.tmp >> ${lf}.store

## Exim analis for no MAIL in SMTP connection

cat ${lf}.tmp|grep 'no MAIL in SMTP connection'| \
awk '{print "insert into exim_db.ddos_nomail value (\""$0"\");"}' \
|${msql_e}
rm -f ${lf}.tmp

и делаем его исполнимым
chmod +x all_log_pars.sh

В root cron добавляем его выполнение по расписанию.
* * * * * /путь/к/вашему/скрипту/all_log_pars.sh

Данный метод анализа и защиты использует таблицы и скрипт из первой части статьи.

Следует обратить внимание, что у таблиц ddos_exp и ddos_analis тип MEMORY, нечего насиловать винт, для временных таблиц.



размещено: 2009-09-28,
последнее обновление: 2010-10-07,
автор: buryanov


opt1k, 2009-11-27 в 11:04:11

начал читать - увидел cvsup.
Ув. автор, я думаю надо срочно исправить его на csup.

opt1k, 2009-11-27 в 11:10:32

А в целом статья хорошая, спасибо, пиши ещё :)

German, 2010-08-20 в 19:46:03

Хотелось бы "добавки" в плане прикручивания exim'a к pf'у(ведь не все пользуют ipfw)

buryanov, 2010-09-14 в 16:58:36

к сожалению, или к счастью, с pf\"ом не знаком, можбыть стоит посмотреть в его сторону, а можбыть ну его, мне и ipfw вполне хватает. Если решусь - то напишу обязательно

Garison, 2010-09-16 в 16:18:46

для новичков типа меня перед добалением триггеров необходимо сделать команду смены окончания команды
DELIMITER |
после END сделать |
и поменять обратно
DELIMITER ;

Олег М., 2013-10-23 в 4:21:37

Перепишите пожалуйста кто может тригеры под MySQL 5.5.34, а то выдает ошибку:
SQL-запрос:
CREATE TRIGGER ddos_analis AFTER INSERT ON ipfw
FOR EACH
ROW
BEGIN
SET @coun = new.coun +1;

#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 7

Заранее огромнейшее спасибо тому кто перепишет.



 

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

© lissyara 2006-10-24 08:47 MSK

Время генерации страницы 0.072 секунд
Из них PHP: 31%; SQL: 69%; Число SQL-запросов: 86 шт.
У Вас отключено GZIP-сжатие в браузере. Размер страницы 45909