Данное решение позволяет вычислять любых ботов, за исключением тех, которые полностью имитируют работу браузера.
Бот запрашивает страницу, например habrahabr.ru/search. Бот не умеет загружать вместе со страницей картинки, скрипты, css и пр. Значит в логе будет отображен запрос к /search/ и всё.
Если на habrahabr.ru/search заходит живой человек через браузер, то вместе с /search/ в лог попадет множество картинок, скриптов, css и пр.
/etc/my.cnf
Под рутом:
sysctl.conf подробно с комментариями (linux)
Ram drive нужен для ускорения работы с логами nginx-а.
Добавляем в файл /etc/fstab
Затем
Берем на сайте любой статичный и ничем не приметный файл (картинка, css, js и пр.), загружаемый при вызове любой страницы динамики, например habrahabr.ru/styles/fontello/css/habr.css
Этот файл нужно сделать некэшируемым, т.е. добавить рандомный параметр, например <?php echo '/styles/fontello/css/habr.css?'.rand(99999999)?>.
Для справки, по умолчанию opera кладет в локальный кэш картинки на 1 час, css/js на 5 минут.
ENGINE=MEMORY — чтобы было быстрее.
Таблица, куда занесем ip поисковых ботов
Таблица заблокированных
Для простоты понимания написано на php, т.к. этот язык знают почти все. И обработка ошибок убрана также — для простоты понимания.
Запускаем:
Помещаем в крон
Вот и всё, боты банятся, люди пропускаются.
В следующих выпусках:
upd
Cписки AS и IP нужно постоянно обновлять.
upd 2
OS: FreeBSD 8.3
CPU: E5-2620 2.00GHz
rows(dinamic_log): 100000 (100 000 http-запросов к динамике сайта за 3 секунды)
rows(hook_log): 1000 (1000 легитимных запросов от пользователей за 3 секунды)
# php /root/scripts/php/imgtest/ddos_hook.php /tmp/d20.log /tmp/h20.log 5 300 3
LOAD DATA time elapsed: 0.29 sec.
LOAD DATA time elapsed: 0.003 sec.
select time elapsed: 0.017 sec.
rows(ban): 1800
full cicle time elapsed: 0.313 sec.
rows(dinamic_log): 1000000 (1 млн. http-запросов к динамике сайта за 3 секунды)
rows(hook_log): 10000 (10 000 легитимных запросов от пользователей за 3 секунды)
# php /root/scripts/php/imgtest/ddos_hook.php /tmp/d2.log /tmp/h2.log 5 300 3
LOAD DATA time elapsed: 2.878 sec.
LOAD DATA time elapsed: 0.023 sec.
select time elapsed: 0.501 sec.
rows(ban): 12402
full cicle time elapsed: 3.54 sec.
P.S.
Описанное в статье решение является экспериментальным. Используйте на свой страх и риск
Как это работает
Бот запрашивает страницу, например habrahabr.ru/search. Бот не умеет загружать вместе со страницей картинки, скрипты, css и пр. Значит в логе будет отображен запрос к /search/ и всё.
Если на habrahabr.ru/search заходит живой человек через браузер, то вместе с /search/ в лог попадет множество картинок, скриптов, css и пр.
Настройка
Mysql
/etc/my.cnf
[mysqld] local-infile=1 #разрешаем load data # устанавливаем максимальный размер мемори: max_heap_table_size=1024M tmp_table_size=1024M
Под рутом:
UPDATE `mysql`.`user` SET `File_priv` = 'Y' WHERE `user`.`Host` = 'localhost' AND `user`.`User` = 'ИМЯ_ЮЗЕРА_БД'; flush privileges;
sysctl
sysctl.conf подробно с комментариями (linux)
Ram drive
Ram drive нужен для ускорения работы с логами nginx-а.
Добавляем в файл /etc/fstab
tmpfs /var/log/ram_disk tmpfs size=1024m 0 0
Затем
mkdir /var/log/ram_disk mount -t tmpfs -o size=1024m tmpfs /var/log/ram_disk
Алгоритм
1. Выбор ловушки
Берем на сайте любой статичный и ничем не приметный файл (картинка, css, js и пр.), загружаемый при вызове любой страницы динамики, например habrahabr.ru/styles/fontello/css/habr.css
Этот файл нужно сделать некэшируемым, т.е. добавить рандомный параметр, например <?php echo '/styles/fontello/css/habr.css?'.rand(99999999)?>.
Для справки, по умолчанию opera кладет в локальный кэш картинки на 1 час, css/js на 5 минут.
2. Правим конфиг nginx
# делаем нужный нам формат лога log_format ddos_log '$remote_addr\t$msec\t$status'; # наша ловушка location =/styles/1347283218/highlight.css { access_log /var/log/ram_disk/hook_access.log ddos_log; } # вся остальная статика location ~* ^.+\.(class|htc|bmp|cur|jpg|jpeg|gif|png|svg|xls|doc|xhtml|js|css|mp3|ogg|mpe?g|avi|flv|zip|gz|bz2?|rar|ico|txt|jar|swf)$ { access_log off; } # динамика location / { access_log /var/log/ram_disk/dynamic_access.log ddos_log; }
3. Создаем таблицы для логов
ENGINE=MEMORY — чтобы было быстрее.
CREATE TABLE `dinamic_log` ( `inc` bigint(20) NOT NULL AUTO_INCREMENT, `remote_addr` varchar(20) NOT NULL DEFAULT '0', `time_local` int(20) NOT NULL DEFAULT '0', `status` int(4) NOT NULL DEFAULT '0', PRIMARY KEY (`inc`), KEY `remote_addr` (`remote_addr`), KEY `time_local` (`time_local`) ) ENGINE=MEMORY AUTO_INCREMENT=1 DEFAULT CHARSET=latin1
CREATE TABLE `hook_log` ( `inc` bigint(20) NOT NULL AUTO_INCREMENT, `remote_addr` varchar(20) NOT NULL DEFAULT '0', `time_local` int(20) NOT NULL DEFAULT '0', `status` int(4) NOT NULL DEFAULT '0', PRIMARY KEY (`inc`), KEY `remote_addr` (`remote_addr`), KEY `time_local` (`time_local`) ) ENGINE=MEMORY AUTO_INCREMENT=1 DEFAULT CHARSET=latin1
Таблица, куда занесем ip поисковых ботов
CREATE TABLE `white` ( `remote_addr` bigint(20) NOT NULL, PRIMARY KEY (`remote_addr`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1
Таблица заблокированных
CREATE TABLE `black` ( `remote_addr` bigint(20) NOT NULL, `time_local` int(20) NOT NULL DEFAULT '0', PRIMARY KEY (`remote_addr`), KEY `time_local` (`time_local`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1
4. Главный скрипт
Для простоты понимания написано на php, т.к. этот язык знают почти все. И обработка ошибок убрана также — для простоты понимания.
// файл лога динамики $dinamic_log = $argv[1]; // файл лога ловушки $hook_log = $argv[2]; // кол-во запросов к динамике без картинок, после которого будем банить. $r_stop = $argv[3]; // период за который делаем анализ (храним данные в таблице) $load_time = $argv[4]; // через сколько сек делаем загрузку лога и анализ $wait_sec = $argv[5]; function load_log($log, $table) { $tmp = '/var/log/ram_disk/tmp_ddos_file'; // копируем лог во временный файл copy ($log, $tmp); // чистим лог file_put_contents($log, "", LOCK_EX); // загружам лог в БД mysql_query('LOAD DATA CONCURRENT INFILE "'.$tmp.'" IGNORE INTO TABLE '.$table.' FIELDS TERMINATED BY \'\t\' (`remote_addr`, `time_local`, `status`) SET `remote_addr` = INET_ATON(`remote_addr`)'); // удалем временный файл unlink($tmp); } // выполняем в бесконечном цикле while (true) { // загружаем лог динамики load_log($dinamic_log, 'dinamic_log'); // загружаем лог ловушки load_log($hook_log, 'hook_log'); // ищем ботов. nginx не дает проверять $status, поэтому 200 и 304 фильтруем здесь. $res = mysql_query('SELECT dinamic_log.remote_addr FROM `dinamic_log` WHERE (`status` = 200 OR `status` = 304) AND`remote_addr` NOT IN (SELECT `remote_addr` FROM `hook_log`) AND`remote_addr` NOT IN (SELECT `remote_addr` FROM `white`) GROUP BY `remote_addr` HAVING count(inc)>'.$r_stop); while ($row = mysql_fetch_array($res)) { // логируем забаненые ip mysql_query('INSERT INTO black(`remote_addr`) VALUES ('.$row['remote_addr'].')'); // блокируем ip switch (PHP_OS) { case "FreeBSD": system('/sbin/route add -host '.$row['remote_addr'].' 127.0.0.1 -blackhole'); break; case "Linux": system('/sbin/ip route add blackhole '.long2ip($row['remote_addr'])); break; } } // чистим таблицу от старых логов mysql_query('DELETE FROM `log` WHERE `time_local` < (UNIX_TIMESTAMP() - '.$load_time.')'); // ждем sleep($wait_sec); }
Запускаем:
php ddoshook.php /var/log/ram_disk/dynamic_access.log /var/log/ram_disk/hook_access.log 5 300 3
5. Разбаниваем
$block_time = $argv[1]; // на какое время баним ip. $res = mysql_query('SELECT `remote_addr` FROM black WHERE time_local < (UNIX_TIMESTAMP() - '.$block_time.')'); while ($row = mysql_fetch_array($res)) { // разблокировка ip switch (PHP_OS) { case "FreeBSD": system('/sbin/route delete '.$row['remote_addr']); break; case "Linux": system('/sbin/ip route delete '.long2ip($row['remote_addr'])); break; } }
Помещаем в крон
* * * * * /usr/bin/php unban.php 86400
Вот и всё, боты банятся, люди пропускаются.
В следующих выпусках:
- Как выдерживать syn/udp/icmp flood на пределах возможностей сервера и канала.
- Как безошибочно определять поисковых ботов, не обращая внимания на юзер-агенты.
- 7 методов, которые помогли отбить более 1000 http ddos-атак.
- Как получить профессиональную защиту от ddos-атак, заплатив всего 5$.
upd
Как узнать IP поисковых ботов?
- Ищем AS нужного поисковика, например здесь: bgp.potaroo.net/cidr/autnums.html
- Получаем IP адреса для AS: stat.ripe.net/data/announced-prefixes/data.json?resource=AS15169
Cписки AS и IP нужно постоянно обновлять.
upd 2
OS: FreeBSD 8.3
CPU: E5-2620 2.00GHz
Тест 1
rows(dinamic_log): 100000 (100 000 http-запросов к динамике сайта за 3 секунды)
rows(hook_log): 1000 (1000 легитимных запросов от пользователей за 3 секунды)
# php /root/scripts/php/imgtest/ddos_hook.php /tmp/d20.log /tmp/h20.log 5 300 3
LOAD DATA time elapsed: 0.29 sec.
LOAD DATA time elapsed: 0.003 sec.
select time elapsed: 0.017 sec.
rows(ban): 1800
full cicle time elapsed: 0.313 sec.
Тест 2
rows(dinamic_log): 1000000 (1 млн. http-запросов к динамике сайта за 3 секунды)
rows(hook_log): 10000 (10 000 легитимных запросов от пользователей за 3 секунды)
# php /root/scripts/php/imgtest/ddos_hook.php /tmp/d2.log /tmp/h2.log 5 300 3
LOAD DATA time elapsed: 2.878 sec.
LOAD DATA time elapsed: 0.023 sec.
select time elapsed: 0.501 sec.
rows(ban): 12402
full cicle time elapsed: 3.54 sec.
P.S.
Описанное в статье решение является экспериментальным. Используйте на свой страх и риск
