Временная блокировка доступа к сайту
В одной из наших прошлых тем - Автобан, или как автоматически заблокировать доступ к сайту, мы рассматривали скрипт, который позволял блокировать доступ к сайту для IP адресов, которые слишком часто обращались к страничкам сайта.
Данная тема пользовалась популярностью и уже немного устарела, так что потребовалось ее немного обновить и добавить пару нововведений. Основным отличием данной темы от предыдущей является то, что теперь IP адреса банятся на указанное время, а не навсегда, как это было раньше. Скрипт был переведен на MySQLi и оптимизирован для уменьшения хранимых в MySQL базе данных и увеличения скорости работы БД.
Была добавлена Блокировка файлов от записи на PHP, которая не позволит одному процессу помешать другому при перезаписи файла .htaccess. Так же файл .htaccess перезаписывается не целиком, а с последней найденной строки "Order Allow,Deny", если таковая имеется. Если данная строка не найдена, то забаненные IP адреса добавляются в конец файла .htaccess.
Для работы нашего скрипта временного автобана необходима поддержка хостингом MySQLi.
В MySQL необходимо будет создать две одинаковые таблицы: all_visits и black_list_ip.
В all_visits мы будем помещать IP адреса посетителей и время их обращения к страничке.
В black_list_ip будут храниться забаненные IP адреса и время, когда они были забанены.
Структура данных таблиц приведена на рисунках ниже:
Таблица black_list_ip.
В обеих таблицах в поле id напротив AUTO_INCREMENT необходимо поставить галочку! При необходимости в дальнейшем данные таблицы могут быть расширены для хранения дополнительной информации (сколько раз банился конкретный IP и т.д.).
После того, как оде таблицы в MySQL были созданы, в самое начало Ваших страничек поместите следующий PHP код:
<?php
$bot='';
$ip=$_SERVER['REMOTE_ADDR'];
if (strstr($_SERVER['HTTP_USER_AGENT'], 'Yandex')) $bot='Yandex';
elseif (strstr($_SERVER['HTTP_USER_AGENT'], 'Google')) $bot='Google';
elseif (strstr($_SERVER['HTTP_USER_AGENT'], 'Yahoo')) $bot='Yahoo';
elseif (strstr($_SERVER['HTTP_USER_AGENT'], 'Mail')) $bot='Mail';
if ($bot=='') {
$db=mysqli_connect("localhost","логин","пароль","имя_бд");
$res=mysqli_query($db,"INSERT INTO all_visits (ip,date) VALUES
(INET_ATON('".$ip."'),'".time()."')");
$res=mysqli_query($db,"SELECT count(id) FROM all_visits WHERE
(ip=INET_ATON('".$ip."') and date>'".(time()-10)."') LIMIT 1");
$count_visit=mysqli_fetch_array($res);
if ($count_visit[0]>10) {
$res=mysqli_query($db,"INSERT INTO black_list_ip (ip,date) VALUES
(INET_ATON('".$ip."'),'".time()."')");
$start_line=0;
$lines='';
$ln_hta='';
$fh=fopen(".htaccess", "a+");
flock($fh, LOCK_EX);
fseek($fh, 0);
while (!feof($fh)) $lines.=fread($fh,2048);
$lines=explode("\n", $lines);
for ($n=0; $n<=count($lines); $n++) {
if (strstr($lines[$n],"Order Allow,Deny")) $start_line=$n;
}
if ($start_line!=0) for ($n=0; $n<$start_line; $n++) $ln_hta[]=$lines[$n];
else $ln_hta=$lines;
$ln_hta[]="Order Allow,Deny";
$ln_hta[]="Allow from all";
$res=mysqli_query($db,"SELECT INET_NTOA(ip) AS ip,date FROM black_list_ip
ORDER BY INET_ATON(ip)");
while ($bad_ip=mysqli_fetch_array($res)) {
if (time()<($bad_ip[date]+900))$ln_hta[]=" deny from ".$bad_ip[ip];
}
$ln_hta=implode("\n",$ln_hta);
ftruncate($fh, 0);
fwrite($fh, $ln_hta);
flock($fh, LOCK_UN);
fclose($fh);
}
}
?>
Данный PHP код, при каждом обращении к страничке, проверяет поисковая система это или нет. Если это не поисковая система, то помещает текущее время и IP адрес в таблицу all_visits.
Далее проверяется, какое количество обращений к сайту было за последние 10 секунд с данного IP адреса. Если количество обращений больше 10, то IP адрес посетителя помещается в таблицу black_list_ip и перезаписывается файл .htaccess, с уже добавленным только что заблокированным IP адресом, тем самым запрещая ему доступ к сайту.
Теперь необходимо чистить MySQL базу от ненужных записей и снимать баны с IP адресов через указанные промежутки времени. Для этого поместите следующий PHP код в файл, который необходимо запускать раз в пять минут по CRON-у (или любым другим способом):
<?php
$db=mysqli_connect("localhost","логин","пароль","имя_бд");
$res=mysqli_query($db,"DELETE FROM black_list_ip WHERE date<".(time()-900)."");
$res=mysqli_query($db,"DELETE FROM all_visits WHERE date<".(time()-900)."");
$start_line=0;
$lines='';
$ln_hta='';
$fh=fopen(".htaccess", "a+");
flock($fh, LOCK_EX);
fseek($fh, 0);
while (!feof($fh)) $lines.=fread($fh,2048);
$lines=explode("\n", $lines);
for ($n=0; $n<=count($lines); $n++) {
if (strstr($lines[$n],"Order Allow,Deny")) $start_line=$n;
}
if ($start_line!=0) for ($n=0; $n<$start_line; $n++) $ln_hta[]=$lines[$n];
else $ln_hta=$lines;
$ln_hta[]="Order Allow,Deny";
$ln_hta[]="Allow from all";
$res=mysqli_query($db,"SELECT INET_NTOA(ip) AS ip,date FROM black_list_ip
ORDER BY INET_ATON(ip)");
while ($bad_ip=mysqli_fetch_array($res)) {
if (time()<($bad_ip[date]+900))$ln_hta[]=" deny from ".$bad_ip[ip];
}
$ln_hta=implode("\n",$ln_hta);
ftruncate($fh, 0);
fwrite($fh, $ln_hta);
flock($fh, LOCK_UN);
fclose($fh);
?>
Таким образом, скрипт позволяет автоматически заблокировать доступ к сайту для тех IP адресов, которые делают более 10 обращений к сайту за 10 секунд. Минусом данного скрипта является то, что в бан могут угодить нужные роботы, HTTP_USER_AGENT которых мы не указали. Стоит учитывать и тот факт, что скрипт дает небольшое увеличение нагрузки на сервер из-за того, что при каждом обращении к страничке в базу записывается время и IP адрес посетителя, после чего идет запрос о количестве обращений за последнее время.
От серьезной атаки или хитрого юзера скрипт конечно не спасет, но зато поможет отсеять много бесполезных ботов и программ качалок, которые используют один IP адрес.
Перед публикацией все комментарии проходят обязательную модерацию!
Если Вы хотите задать какой-либо вопрос, то сделайте это на нашем форуме.
Таким образом, Вы сможете быстрее получить ответ на интересующий Вас вопрос.
if ($count_visit[0] == 1){
mysqli_query($db, "DELETE FROM black_list_ip WHERE date<".(time()-(3600*24*3))."");
}
То есть через три дня, очищается при первом обращении к странице.
Может это тоже не лучший вариант, но все просто и прозрачно.
Ведь в этом случае кол-во обращений к серверу будем минимально, и код на сайт вставлять не нужно будет.
А ведь можно тогда проверять файл логов каждый час, а не раз в сутки и эффект сохраниться!
Как по мне, если в .htacess добавляется deny from 0.0.0.0.0 то и timestamp можно добавить рядом после ip как комментарий
deny from 192.65.1.11 #1548624815
и следующий раз искать ip в файле, и сразу же брать и время.
Это будет быстрее и менее затратно.
Также открытие и добавления записей лучше использовать file_get_contents и file_put_contents вместо fopen
fopen используется вместо file_get_contents потому что поддерживает блокировку файлов, а для .htaccess это очень важно, т.к. ошибка в нем при одновременной записи/чтении двумя процессами может "положить" весь сайт (уже имелся негативный опыт).
"Ошибка запроса:
#1075 - Incorrect table definition; there can be only one auto column and it must be defined as a key"
Или пересоздайте таблицу заново, не забывая у поля id напротив auto_increment поставить галочку.
Может дело в том, что у меня не mysqli а mysql?
Если в файл .htaccess IP адреса не добавляются, то скорее всего проблема с БД. Если же в .htaccess появляются новые IP адреса, то значит проблема в .htaccess.
Тот же вопрос относительно удаления адресов через 15 минут - не маловато ли? То есть получается, бот скачал более 10 страниц за 10 секунд и он забанился всего лишь на 15 минут? После этих 15 минут опять зашёл, скачал 10 страниц, потом опять и опять и т.д.
Соответственно, можно ли сделать так?:
(ip=INET_ATON('".$ip."') and date>'".(time()-10)."') LIMIT 1");
if ($count_visit[0]>5)
if (time()<($bad_ip[date]+86400))$ln_hta[]=" deny from ".$bad_ip[ip];
$res=mysqli_query($db,"DELETE FROM black_list_ip WHERE date<".(time()-86400)."");
$res=mysqli_query($db,"DELETE FROM all_visits WHERE date<".(time()-86400)."");
if (time()<($bad_ip[date]+86400))$ln_hta[]=" deny from ".$bad_ip[ip];
Если бот качает по 10 страниц за 10 секунд, то он не будет ждать 15 минут, чтобы продолжить сканирование, тем более он не знает на сколько его заблокировали.
Если захотят скачать сайт, то все равно это сделают... Сам сталкивался с тем, что качают сразу с нескольких десятков IP и у каждого интервал между обращениями более 3 секунд...
Интервал и время можно делать как Вам удобнее. И как писал в последнем абзаце, данный скрипт нельзя считать серьезной защитой.
Сделал всё по инструкции, всё работает. Но возникла такая ситуация: Дело в том, что в моём файле htaccess уже давно есть строка Order Allow,Deny и после неё шло много запрещённых ip, которые я собирал долгое время (айпишники всяких спамеров и т.д.). И после того как по CRON-у сработал файл, очищающий MySQL и htaccess от устаревших записей, то стёрлись ВСЕ запрещающие записи, идущие после Order Allow,Deny Allow from all. То есть, стёрлись и те ip-адреса, которые я добавлял вручную.
Что нужно прописать, чтобы удалялись только те адреса, которые побывали в black_list_ip, а другие не трогались?
Как вариант, можно добавить сразу две директивы Order Allow,Deny Allow from all в файл .htaccess (но самое главное, в самый конец файла!)
Тогда файл .htaccess будет выглядеть так:
# Здесь ваши основные правила - перенаправления и т.д.
Order Allow,Deny
Allow from all
Deny from 111.111.111.111
Deny from 222.222.222.222
Order Allow,Deny
Allow from all
Таким образом, в более верхний блок Вы будете записывать правила вручную, а айпишники из скрипта будут автоматом записываться и удаляться после самого нижнего правила.
То есть, нужно указать (time()-10) и ($count_visit[0]>6)
Помимо этого я также немного улучшил код, добавив отправку письма Вам на почту в случае срабатывания скрипта.
Для этого нужно после
if ($count_visit[0]>6) {
$res=mysqli_query($db,"INSERT INTO black_list_ip (ip,date) VALUES (INET_ATON('".$ip."'),'".time()."')");
вставить:
$tmestamp = time();
$datum = date("H:i:s d.m.Y",$tmestamp);
$to = "Ваш_email";
$subject = "Сработал автобан";
$msg = "Пойман на странице $REQUEST_URI $datum, IP: $REMOTE_ADDR, User-агент $HTTP_USER_AGENT, Метод $REQUEST_METHOD, Строка запросов, если есть, с помощью которой была получена страница $QUERY_STRING, Адрес страницы, если есть, которая привела браузер пользователя на эту страницу $HTTP_REFERER, Удаленный хост, если есть, с которого пользователь просматривал текущую страницу $REMOTE_HOST";
mail($to, $subject, $msg);
Благодарю за полезный скрипт. Всем удачи!
Если проект с хорошей посещалкой, то БД будет расти, а скорость работы падать.
Вот такое же бы, но через механизм сессий...