Попытка защитить свой почтовый сервер от DDoS атаки [2009]
Друг попросил о помощи, его почтовик подвергся
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
[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. Запуск осуществляется командой
олее детально установка 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 создаём макрос:
добавляем в секции проверок, в которых идут следующие запреты:
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
В триггере присутствуют комментарии, поэтому писать не буду ничего
update exim_db.ddos_exp \ set active = 2 \ WHERE \ LEFT(date_delete, 16) < NOW() and active = 1; \
SELECT "ipfw table 125 add " asdf, ip FROM exim_db.ddos_exp \ WHERE \ active = 0; \
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 = 0; \
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.%';