Сейчас: 00:15:53   19-го апреля 2024 г.
10
48

Временная блокировка доступа к сайту

В одной из наших прошлых тем - Автобан, или как автоматически заблокировать доступ к сайту, мы рассматривали скрипт, который позволял блокировать доступ к сайту для 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 адреса и время, когда они были забанены.


Структура данных таблиц приведена на рисунках ниже:


Таблица, содержащая IP адрес посетителя и время обращения к страничке Таблица all_visits.

Таблица, содержащая забаненные IP адреса и время бана Таблица black_list_ip.

В обеих таблицах в поле id напротив AUTO_INCREMENT необходимо поставить галочку! При необходимости в дальнейшем данные таблицы могут быть расширены для хранения дополнительной информации (сколько раз банился конкретный IP и т.д.).


После того, как оде таблицы в MySQL были созданы, в самое начало Ваших страничек поместите следующий PHP код:

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($fhLOCK_EX);
 
fseek($fh0);
 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($fh0);
  
fwrite($fh$ln_hta);
  
flock($fhLOCK_UN);
  
fclose($fh);
 }
}
?>

Данный PHP код, при каждом обращении к страничке, проверяет поисковая система это или нет. Если это не поисковая система, то помещает текущее время и IP адрес в таблицу all_visits.


Далее проверяется, какое количество обращений к сайту было за последние 10 секунд с данного IP адреса. Если количество обращений больше 10, то IP адрес посетителя помещается в таблицу black_list_ip и перезаписывается файл .htaccess, с уже добавленным только что заблокированным IP адресом, тем самым запрещая ему доступ к сайту.


Теперь необходимо чистить MySQL базу от ненужных записей и снимать баны с IP адресов через указанные промежутки времени. Для этого поместите следующий PHP код в файл, который необходимо запускать раз в пять минут по CRON-у (или любым другим способом):

PHP код:
<?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($fhLOCK_EX);
fseek($fh0);
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($fh0);
fwrite($fh$ln_hta);
flock($fhLOCK_UN);
fclose($fh);
?>

Таким образом, скрипт позволяет автоматически заблокировать доступ к сайту для тех IP адресов, которые делают более 10 обращений к сайту за 10 секунд. Минусом данного скрипта является то, что в бан могут угодить нужные роботы, HTTP_USER_AGENT которых мы не указали. Стоит учитывать и тот факт, что скрипт дает небольшое увеличение нагрузки на сервер из-за того, что при каждом обращении к страничке в базу записывается время и IP адрес посетителя, после чего идет запрос о количестве обращений за последнее время.


От серьезной атаки или хитрого юзера скрипт конечно не спасет, но зато поможет отсеять много бесполезных ботов и программ качалок, которые используют один IP адрес.

Дата создания: 16:13:05 27.10.2013 г.
Посещений: 13994 раз(а).

Комментарии посетителей (37 шт.):
Pisatel
1
# 1376
(16:29:21  28.12.2013 г.)

Можно без крона, что-то типа
if ($count_visit[0] == 1){
mysqli_query($db, "DELETE FROM black_list_ip WHERE date<".(time()-(3600*24*3))."");
}
То есть через три дня, очищается при первом обращении к странице.
Ответить

Administrator
-1
# 1377
(17:01:08  28.12.2013 г.)

В Вашем случае получается, что MySQL запрос на удаление устаревших записей (старше трех дней) будет выполняться при заходе на сайт любого IP адреса, которого нет в таблице all_visits. Но, при высокой посещаемости это нецелесообразно, т.к. CRON можно выполнять, например, раз в час, а по Вашему способу, число запросов на удаление за час может быть в десятки, а то и в сотни раз больше.
Ответить

Pisatel
0
# 1378
(17:34:32  28.12.2013 г.)

Думаю, это не очень большая проблема: добавляется всего один запрос от одного посетителя. Причем не важно, сколько страниц он просмотрит: запрос все равно будет один. И если у вас посещаемость 1000-3000 хостов, то на производительности это не скажется. А если она намного выше - использовать подобный скрипт вообще нецелесообразно, так как нагрузка от него будет существенной. Крон - это хорошо, возможно даже лучше предложенного мной. Однако, большинство любит просто "поставить и забыть", а с кроном нужно еще моск включать немного:-)
Ответить

Administrator
0
# 1380
(17:39:05  28.12.2013 г.)

А чтобы не ломать мозг по поводу CRON-а, у нас уже имеется тема - Аналог CRON-а на PHP
Может это тоже не лучший вариант, но все просто и прозрачно.
Ответить

Pisatel
0
# 1379
(17:37:07  28.12.2013 г.)

А вообще, я это писал для очистки таблицы all_visits, так она пухнет до безобразия за неделю.
Ответить

Егор
0
# 1550
(12:55:50  29.03.2014 г.)

А может, чтобы нагрузка не увеличивалась, просто убрать обращения к базе данных и информацию брать с файла логов?
Ответить

Administrator
0
# 1551
(15:35:06  29.03.2014 г.)

Еще неизвестно, от чего нагрузка будет выше, т.к. файл логов нужно будет каждый раз считывать и производить в нем поиск, плюс необходимо будет делать блокировки, чтобы не потерять содержимое файла.
Ответить

Егор
0
# 1552
(12:54:13  30.03.2014 г.)

Хорошо спасибо! И все-таки, например файл логов создается один на каждый день. Что есть сделать так, чтобы логи проверялись только один раз в сутки и на основании данных только одних суток, если к примеру с одного ip было больше 300 обращений в сутки, этот скверный ip бы блокировался, и в файл htaccess добавлялась запись deny from ip. Можно такое реализовать?
Ведь в этом случае кол-во обращений к серверу будем минимально, и код на сайт вставлять не нужно будет.
Ответить

Administrator
0
# 1553
(16:09:33  30.03.2014 г.)

Конечно, такое можно реализовать, но тогда теряется смысл от такой блокировки, т.к. боту хватит и часа, чтобы не торопясь выкачать сайт из 1000 страничек целиком. И какой тогда смысл банить его в конце дня.
Ответить

Егор
0
# 1555
(19:54:30  30.03.2014 г.)

Я столкнулся немного с другой проблемой, у меня нагрузка на сервер возрастает из-за того, что каждый день один и тот же бот, грузит сайт. В сутки где-то 2000-3000 запросов делает, причем может одну страницу долбить часами. Хостер за это не меня ругается, вот я и вынужден искать решение.
А ведь можно тогда проверять файл логов каждый час, а не раз в сутки и эффект сохраниться!
Ответить

Administrator
1
# 1557
(21:25:40  30.03.2014 г.)

Ну, не знаю, бот и за час успеет делов натворить, а потом придет с другого IP и т.д. А чем Вам этот скрипт не подходит? Его тоже можно настроить, чтобы он по CRON-у раз в час банил очень активных.
Ответить

Егор
0
# 1569
(12:15:19  02.04.2014 г.)

Ну как я понял текущий скрипт наполняет базу данных, заставляет сайт грузить сервер.
Ответить

Илья
0
# 1649
(20:43:48  07.05.2014 г.)

Можете подсказать, почему в таблице all_visits IP адрес записывается в таком формате: "89126242"? А в файл .htaccess попадает в нормальном виде: 5.79.245.98. Для примера указал свой реальный IP.
Ответить

Administrator
0
# 1650
(20:58:06  07.05.2014 г.)

Это сделано намеренно для того чтобы уменьшить размер базы и увеличить скорость поиска IP адресов, т.к. хранить IP адрес в базе с типом данных INT намного выгоднее, чем с типом VARCHAR(15). Для преобразования используются стандартные функции MySQL INET_ATON() и INET_NTOA().
Ответить

Pisatel
0
# 2309
(17:22:54  09.03.2015 г.)

flock() не нужен при чтении и записи через fopen; fread() - лучше использовать file_get_contents- эта функция быстрее, fseek тоже не нужен, так как мы уже пишем в конец файла. И лучше ставить fopen($fh, "a+b"); и проверять, удачно ли открылся файл, то есть типа if (!fopen($fh, "a+b")) { exit; } else { ....}. Перед записью также лучше проверять, будет ли "писАться" файл: if (is_writable(".htaccess")) { fwrite(); } else { exit; }. Ну и последний вопрос: для чего каждый раз перезаписывать полностью файл? Может, проще удалять всё с Order allow,deny..., и записывать только с этого места?
Ответить

Anton
0
# 2553
(10:43:40  29.06.2015 г.)

Скрипт совместим с плагином кэширования?
Ответить

Андрей
0
# 2787
(17:32:06  17.10.2015 г.)

Если мне на вход надо поставить защиту от перебора пароля - бан по ip на 3 минуты после пятой неудачной попытки, то я должен вносить изменения именно в контролер авторизации (login.php) или в модель (M_Users)? И что именно надо прописать или где можно посмотреть пример? Архитектура MVC.
Ответить

Максим
1
# 3122
(15:12:09  27.03.2016 г.)

Какой смысл таблицы black_list_ip? Зачем создавать целую таблицу и лишний запрос ради двух значений, один из которых уже известен?
Как по мне, если в .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
Ответить

Administrator
0
# 3123
(15:37:13  27.03.2016 г.)

В black_list_ip содержатся все заблокированные IP адреса и время их блокировки, а их могут быть сотни и тысячи в зависимости от настроек скрипта и времени блокировки. Для многих удобнее работать с MySQl, чем с файлами. Можно вообще отказаться от MySQl, но вот насколько это будет удобно? Тут уж каждый сам решает.
fopen используется вместо file_get_contents потому что поддерживает блокировку файлов, а для .htaccess это очень важно, т.к. ошибка в нем при одновременной записи/чтении двумя процессами может "положить" весь сайт (уже имелся негативный опыт).
Ответить

Игорь
0
# 3140
(19:29:23  31.03.2016 г.)

AUTO_INCREMENT не создается.

"Ошибка запроса:
#1075 - Incorrect table definition; there can be only one auto column and it must be defined as a key"
Ответить

Administrator
0
# 3141
(22:01:47  31.03.2016 г.)

ALTER TABLE Table MODIFY ID <type> NOT NULL AUTO_INCREMENT;
Или пересоздайте таблицу заново, не забывая у поля id напротив auto_increment поставить галочку.
Ответить

PHPSID
1
# 3164
(19:38:48  06.04.2016 г.)

Неплохой скриптик, но все же NGINX логов and iptables ничего нет лучше. Вот вам нормальное определение реального IP за прокси Opera, UcWeb, Ovi и т.п., где используется сжатие трафика.
Ответить

Николай
0
# 3310
(05:18:51  06.07.2016 г.)

А как и куда его вставить?
Ответить

Administrator
0
# 3311
(20:01:19  06.07.2016 г.)

Добавляете в начало PHP кода, а затем вместо $ip=$_SERVER['REMOTE_ADDR']; используете $ip=getRealIP();
Ответить

Андрей
0
# 3178
(14:47:23  20.04.2016 г.)

В таблицах тип поля ip с INT на BIGINT поменял, а то в него часто максимально допустимое значение 2147483647 записывалось.
Ответить

Александр
0
# 3180
(03:19:40  24.04.2016 г.)

Все работает если исполнять скрипты вручную, прописывая в браузере путь site.ru/avtoban.php. Вставляю на сайт в шаблон страницы tpl и он не понимает код. Пробовал подключать <?php include "avtoban.php"; ?> не работает.
Ответить

abc
0
# 3543
(11:56:16  08.11.2016 г.)

Не робит. Подключаю в joomla в корневой index.php и в разные места.
Может дело в том, что у меня не mysqli а mysql?
Ответить

Administrator
0
# 3545
(12:35:16  08.11.2016 г.)

Это смотря как Вы подключаетесь к БД (как к mysqli или как к mysql).
Если в файл .htaccess IP адреса не добавляются, то скорее всего проблема с БД. Если же в .htaccess появляются новые IP адреса, то значит проблема в .htaccess.
Ответить

alex
0
# 3598
(11:15:12  30.11.2016 г.)

А как можно ограничить по количеству запросов в сутки с одного IP. Сейчас боты атакуют с промежутком в 4-5 секунд и скрипт уже не спасает.
Ответить

Administrator
0
# 3601
(20:37:38  30.11.2016 г.)

Конечно можно. Удаляйте из БД записи старше 86400 секунд (24 часа) и в запросе SELECT count(id) замените (time()-10) на (time()-86400). В if ($count_visit[0]>10) значение 10 замените на нужное Вам количество посещений в сутки.
Ответить

Александр
0
# 4147
(10:39:28  23.11.2017 г.)

Здравствуйте! Скажите пожалуйста, значение "10 обращений за 10 секунд" это объективно оправданное значение, полученное опытным путём или же это просто для примера? Просто мне кажется это совсем уж многовато и лояльно. Не лучше ли сделать 5 обращений за 10 секунд, как Вы считаете? Или это совсем жёстко будет?
Тот же вопрос относительно удаления адресов через 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];
Ответить

Administrator
0
# 4149
(22:11:03  24.11.2017 г.)

Значение взято для примера, т.к. все индивидуально и зависит от конкретного сайта и сервера, на котором он размещен. Некоторые пользователи, например, сначала могут поочередно открыть 10 страничек, а уже только потом их читать.
Если бот качает по 10 страниц за 10 секунд, то он не будет ждать 15 минут, чтобы продолжить сканирование, тем более он не знает на сколько его заблокировали.
Если захотят скачать сайт, то все равно это сделают... Сам сталкивался с тем, что качают сразу с нескольких десятков IP и у каждого интервал между обращениями более 3 секунд...
Интервал и время можно делать как Вам удобнее. И как писал в последнем абзаце, данный скрипт нельзя считать серьезной защитой.
Ответить

Павел
0
# 4185
(00:02:19  12.12.2017 г.)

Здравствуйте!
Сделал всё по инструкции, всё работает. Но возникла такая ситуация: Дело в том, что в моём файле htaccess уже давно есть строка Order Allow,Deny и после неё шло много запрещённых ip, которые я собирал долгое время (айпишники всяких спамеров и т.д.). И после того как по CRON-у сработал файл, очищающий MySQL и htaccess от устаревших записей, то стёрлись ВСЕ запрещающие записи, идущие после Order Allow,Deny Allow from all. То есть, стёрлись и те ip-адреса, которые я добавлял вручную.
Что нужно прописать, чтобы удалялись только те адреса, которые побывали в black_list_ip, а другие не трогались?
Ответить

Зам. админа
0
# 4194
(17:52:14  14.12.2017 г.)

Здравствуйте, Павел!
Как вариант, можно добавить сразу две директивы 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

Таким образом, в более верхний блок Вы будете записывать правила вручную, а айпишники из скрипта будут автоматом записываться и удаляться после самого нижнего правила.
Ответить

Андрей
0
# 4213
(18:37:11  30.12.2017 г.)

Путём экспериментов выяснил, что оптимальное соотношение это 6 запросов за 10 секунд.
То есть, нужно указать (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);

Благодарю за полезный скрипт. Всем удачи!
Ответить

Anton
0
# 4979
(23:37:48  27.04.2023 г.)

В БД все результаты добавляются, а в .htaccess нет. В чем может быть проблема?
Ответить

Альберт
0
# 5023
(12:31:17  31.10.2023 г.)

Скрипт конечно интересный, но делает записи в БД.
Если проект с хорошей посещалкой, то БД будет расти, а скорость работы падать.
Вот такое же бы, но через механизм сессий...
Ответить

Закрыть
Ваше имя:
309 + 18 =
Добавить комментарий:
Ваше имя:
309 + 18 =

Перед публикацией все комментарии проходят обязательную модерацию!

Если Вы хотите задать какой-либо вопрос, то сделайте это на нашем форуме.
Таким образом, Вы сможете быстрее получить ответ на интересующий Вас вопрос.
Посетителей онлайн: 4

Какие темы необходимо добавлять на сайт?