Безопасность и снижение нагрузки.

Автор Yworld_garry, 12 сентября 2009, 00:58:10

« назад - далее »

0 Пользователи и 1 гость просматривают эту тему.

Yworld_garry

Что думаете по этому поводу?
Есть проблемка именно такого рода у одного проекта, стоит ли прикрутить?
Ограничение на скорость выдачи страниц одному пользователю Андрей Викторович Якушев.

ЦитироватьСлучилось мне однажды столкнуться с ситуацией, когда мой хостер предъявил мне претензию о том, что мой акаунт создаёт непомерно большую нагрузку на MySQL-сервер. Посмотрев логи, я заметил, что такую нагрузку создают программы-качалки, которые копируют сайт целиком на локальный компьютер. Во время обращения к странице происходит несколько sql-запросов к базе данных. А если учесть, что эти программы готовы скачивать сразу несколько страниц с сайта, то получается, что в секунду идёт от 3 до 10 запросов. При такой «атаке» серверу действительно приходится не сладко.

Решением я увидел ограничение доступа к сайту с одного ip-адреса чаще, чем один раз в 2 секунды. (Это значение можно регулировать). Проверка происходит без использования sql-сервера, поэтому идёт достаточно быстро.

Здесь я предлагаю php-скрипт, реализующий такую проверку. Подключение этого модуля нужно вставлять в самом начале каждой страницы.

Вторую версию скрипта породила ошибка, появляющаяся при одновременном обращении к файлу, хранящему информацию о последних посетителях. Во второй версии посещения записываются в виде файлов нулевой длины в специальную временную директорию. Кроме того, изменён способ учёта интервала: если раньше интервал считался от первого обращения, то теперь от последнего. Таким образом, если раньше при «бомбардировке» сайта раз в несколько секунд страница всё-таки отдавалась, то теперь нормальный показ возобновится лишь после того, как будет полностью выдержан установленный интервал времени.

Третья версия скрипта включает в себя два коренных изменения. Во-первых, если посетитель продолжает атаковать ваш сайт, то через определённое количество запросов его ip-адрес будет внесёт в «чёрный список» файла .htaccess, и ему будет полностью закрыт доступ к сайту. Запрет через .htaccess практически не отнимает у процессора время. Во-вторых, проверка захода поисковых роботов теперь ведётся не по полю HTTP_USER_AGENT, а по ip-адресу. Для этого есть две причины. Первая из них — это то, что, например, поисковая система Aport не всегда подписывается. Наверное, это из-за того, что они боятся, что для их робота будут выдаваться другие страницы, нежели для посетителя-человека. Интересно, что более популярные поисковики не опускаются до такой паранойи. Вторая причина в том, что некоторые программы-качалки ухитряются выдавать себя за поисковых роботов. А вот это уже серьёзно. Поэтому было принято решение пропускать мимо этого скрипта все запросы с ip-адресов, принадлежащим компаниям-поисковикам, т.к. нет уверенности в точных адресах роботов и в том, что эти адреса не будут меняться.
<?php
    /*
    *--------------------------------------------------------
    * Модуль antiddos.php V3.0.5 Пт 04 Сентябрь 2009
    * Copyright (C) Андрей Якушев, 2006. http://avy.ru
    *--------------------------------------------------------
    * Модуль предназначен для ограничения доступа к сайту или
    * к страницам, где он включён.
    * Принцип работы в том, что запоминается ip-адрес и время
    * обращения с этого адреса. И если в течение заданного
    * времени происходит обращение с того же адреса, то ему
    * выдаётся ошибка 503.
    * Если количество недопустимых обращений подряд превышает
    * определённое число, ip-адрес закрывается через файл
    * .htaccess
    * Модуль необходимо подключать к скрипту самым первым.
    * Этим обеспечивается быстрота его работы.
    *--------------------------------------------------------
    */

    /* Директория для временных файлов. Необходимо указать
       отдельную директорию, т.к. большое количество файлов
       в одной папке змедляет скорость обращения к ней. */
    define ('AD_DIRNAME', $_SERVER['DOCUMENT_ROOT'] . '/tmp_path');
    /* Время задержки в секуднах, в течение которого нельзя
       обращаться к сайту. */
    define ('AD_DELAY', 2);
    /* Количество запрещённых повторений, после которых ip-адрес
       будет забанен. Нужно обратить внимание на то, что некоторые
       программы чтения RSS-каналов считывают все ссылки, помещённые
       в канале сразу. Поэтому, если на сайте есть такие каналы,
       это число необходимо поставить больше, чем максимальное
       количество элементов в канале. */
    define ('AD_TRYING', 35);

    /*
    *---------------------------------------------------------------
    * Список поисковых роботов.
    * Очень не хорошо, если поисковый робот будет натыкаться
    * на ошибки на сайте. Ему это может сильно не понравиться.
    * Поэтому пишем список ip-адресов роботов; добавляем или
    * удаляем, что нужно.
    * IP-адреса:
    * Alta Vista - см. Yahoo
    * Aport - 194.67.18.
    * Gigabot - 66.231.188. (66.231.188.0/24)
    * Google - 209.85.128.0 - 209.85.255.255 (209.85.128.0/17),
    *    72.14.192.0 - 72.14.255.255 (72.14.192.0/18),
    *    66.249.64.0 - 66.249.95.255 (66.249.64.0/19),
    *    64.68.80.0 - 64.68.87.255 (64.68.80.0/21),
    *    66.102.0.0 - 66.102.15.255 (66.102.0.0/20),
    *    64.233.160.0 - 64.233.175.255 (64.233.160.0/19),
    *    216.239.32.0 - 216.239.63.255 (216.239.32.0/19)
    * Mail.Ru - 195.239.211.0 (195.239.211.0/24),
    *    94.100.181.128 - 94.100.181.255 (94.100.181.128/25)
    * msnbot - 65.52.0.0 - 65.55.255.255 (65.52.0.0/14)
    * Rambler - 81.19.64.0 - 81.19.66.255 (81.19.64.0/19)
    * Yahoo - 74.6.0.0 - 74.6.255.255 (74.6.0.0/16),
    *    69.147.64.0 - 69.147.127.255 (69.147.64.0/18)
    * Yandex - 213.180.214.128 - 213.180.214.255 (213.180.192.0/19),
    *    77.88.22.0 - 77.88.23.255 (77.88.0.0/18)
    *    93.158.128.0 - 93.158.191.255 (93.158.128.0/18)
    *    95.108.128.0 - 95.108.255.255 (95.108.128.0/17)
    * LiveInternet - 88.212.202.0 - 88.212.202.63 (88.212.202.0/26)
    * Ip-адреса и маски к ним кодируются в виде строки из 4 символов
    * Это сделано из-за того, что невозможно в PHP использовать
    * стандартными (простыми) процедурами 32-битное целое число
    * без знака. А использование специальных библиотек усложнит
    * работу и сделает скрипт зависимым от этих библиотек.
    *---------------------------------------------------------------
    */
    $ad_Robots_IP = array(
       'Aport'      => array(
          sprintf('%c%c%c%c', 194, 67, 18, 0),
          sprintf('%c%c%c%c', 255, 255, 255, 0)
       ),
       'Gigabot'   => array(
          sprintf('%c%c%c%c', 66, 231, 188, 0),
          sprintf('%c%c%c%c', 255, 255, 255, 0)
       ),
       'Google1'   => array(
          sprintf('%c%c%c%c', 209, 85, 128, 0),
          sprintf('%c%c%c%c', 255, 255, 128, 0)
       ),
       'Google2'   => array(
          sprintf('%c%c%c%c', 72, 14, 192, 0),
          sprintf('%c%c%c%c', 255, 255, 192, 0)
       ),
       'Google3'   => array(
          sprintf('%c%c%c%c', 66, 249, 64, 0),
          sprintf('%c%c%c%c', 255, 255, 224, 0)
       ),
       'Google4'   => array(
          sprintf('%c%c%c%c', 64, 68, 80, 0),
          sprintf('%c%c%c%c', 255, 255, 248, 0)
       ),
       'Google5'   => array(
          sprintf('%c%c%c%c', 66, 102, 0, 0),
          sprintf('%c%c%c%c', 255, 255, 240, 0)
       ),
       'Google6'   => array(
          sprintf('%c%c%c%c', 64, 233, 160, 0),
          sprintf('%c%c%c%c', 255, 255, 224, 0)
       ),
       'Google7'   => array(
          sprintf('%c%c%c%c', 216, 239, 32, 0),
          sprintf('%c%c%c%c', 255, 255, 224, 0)
       ),
       'Mail.Ru1'   => array(
          sprintf('%c%c%c%c', 195, 239, 211, 0),
          sprintf('%c%c%c%c', 255, 255, 255, 0)
       ),
       'Mail.Ru2'   => array(
          sprintf('%c%c%c%c', 94, 100, 181, 128),
          sprintf('%c%c%c%c', 255, 255, 255, 128)
       ),
       'msnbot'   => array(
          sprintf('%c%c%c%c', 65, 52, 0, 0),
          sprintf('%c%c%c%c', 255, 252, 0, 0)
       ),
       'Rambler'   => array(
          sprintf('%c%c%c%c', 81, 19, 64, 0),
          sprintf('%c%c%c%c', 255, 255, 224, 0)
       ),
       'Yahoo1'   => array(
          sprintf('%c%c%c%c', 74, 6, 0, 0),
          sprintf('%c%c%c%c', 255, 255, 0, 0)
       ),
       'Yahoo2'   => array(
          sprintf('%c%c%c%c', 69, 147, 64, 0),
          sprintf('%c%c%c%c', 255, 255, 192, 0)
       ),
       'Yandex1'   => array(
          sprintf('%c%c%c%c', 213, 180, 192, 0),
          sprintf('%c%c%c%c', 255, 255, 224, 0)
       ),
       'Yandex2'   => array(
          sprintf('%c%c%c%c', 77, 88, 0, 0),
          sprintf('%c%c%c%c', 255, 255, 192, 0)
       ),
       'Yandex3'   => array(
          sprintf('%c%c%c%c', 93, 158, 128, 0),
          sprintf('%c%c%c%c', 255, 255, 192, 0)
       ),
       'Yandex4'   => array(
          sprintf('%c%c%c%c', 95, 108, 128, 0),
          sprintf('%c%c%c%c', 255, 255, 128, 0)
       ),
       'LiveInternet' => array(
          sprintf('%c%c%c%c', 88, 212, 202, 0),
         sprintf('%c%c%c%c', 255, 255, 255, 192)
       ),
    );

    /*
    *----------------------------------------------------------
    * Функция создаёт в указанной директории файл, начинающийся
    * с буквы a (для отличия от других возможных файлов) и
    * содержащий в имени ip-адрес клиента.
    * Если количество обращений подряд с данного адреса
    * превысило допустимый предел, адрес закрывается через файл
    * .htaccess
    * При желании может быть отослано письмо на специальный
    * адрес, в котором будет передана информация о действиях
    * с заблокированного адреса.
    *----------------------------------------------------------
    */
    function ad_WriteIP($counter)
    {
       $counter++;
       if ($counter > AD_TRYING)
       {
          //Баним ip-адрес
          $f = fopen($_SERVER['DOCUMENT_ROOT'] . '/.htaccess', 'a');
          fwrite($f, "\ndeny from " . $_SERVER['REMOTE_ADDR']);
          fclose($f);
          // Открыть комментарии, если нужно уведомлять по почте
          /*
          //Получаем хост (в некоторых логах он может быть вместо ip
          $host = gethostbyaddr($_SERVER['REMOTE_ADDR']);
          $mess = 'Заблокирован адрес ' . $_SERVER['REMOTE_ADDR'] .
             ' (' . $host . ")\n\n";
          //Выполняем запрос к логам. Нужно указать путь и имя лог-файла
          exec('cat /home/your_dir/logs/access_log | egrep \'(' .
             str_replace('.', '\\.', $_SERVER['REMOTE_ADDR']) . ')|(' .
             str_replace('.', '\\.', $host) . ')\' | sort -k 4 >' .
             AD_DIRNAME . '/dump.txt');
          $mess .= file_get_contents(AD_DIRNAME . '/dump.txt');
          @ unlink(AD_DIRNAME . '/dump.txt');
          $email = 'my_box@myhost.ru'; //Укажите свой e-mail
          mail($email, 'IP-address was banned', $mess, "From: " . $email .
             "\nReply-To: " . $email .
             "\nContent-Type: text/plain; charset=Windows-1251" .
             "\nContent-Transfer-Encoding: 8bit");
          */
       }
       else
       {
          //Записываем файл с ip-адресом и количеством обращений
          $f = fopen(AD_DIRNAME . '/a' . $_SERVER['REMOTE_ADDR'] . '_' .
             $counter, 'w');
          fclose($f);
       }
    }
    /*
    *----------------------------------------------------
    * Проверка на отношение ip-адреса к сетям поисковиков
    *----------------------------------------------------
    */
    $ad_IsRobot = false;
    $ad_IP = explode('.', $_SERVER['REMOTE_ADDR']);
    $ad_IPMatch = sprintf('%c%c%c%c', $ad_IP[0], $ad_IP[1], $ad_IP[2], $ad_IP[3]);
    foreach ($ad_Robots_IP as $ad_match)
    {
       //Если на входящий адрес наложить маску операцией "и",
       //то он должен будет совпасть с начальным адресом сети.
       if (($ad_IPMatch & $ad_match[1]) == $ad_match[0])
       {
          $ad_IsRobot = true;
          break;
       }
    }

    /*
    *---------------------------------------------------------
    * Поисковые роботы не любят, когда к адресу страницы
    * добавляется переменная сессии. Поэтому, если на сайте
    * используются сессии, то их лучше включать, если агент -
    * не робот.
    * Если сессии не используются, то этот кусок можно убрать.
    *---------------------------------------------------------
    */
    /*
    if (!$ad_IsRobot)
    {
       session_start();
    }
    */

    if (!$ad_IsRobot)
    {
       /*** Чтение каталога и удаление старых файлов ***/
       $ad_dir      = opendir(AD_DIRNAME) or
          die('Отсутствует директория для временных файлов');
       $ad_forbid   = time() - AD_DELAY;
       /* IP-адрес в имени файла, начинающегося на букву a,
       а время обращения - время изменения файла */
       $ad_before_trying = 0;
       while (false !== ($ad_FName = readdir($ad_dir)))
       {
          if (ereg('^a[1-9]', $ad_FName))
          {
             if (@ filemtime(AD_DIRNAME . '/' . $ad_FName) < $ad_forbid){
                @ unlink(AD_DIRNAME . '/' . $ad_FName);
             }
             elseif (ereg('^a' . str_replace('.', '\\.',
                $_SERVER['REMOTE_ADDR']) . '_([0-9]+)$', $ad_FName, $ad_match))
             {
                //Если файл есть, то читаем, сколько раз к нму обращались
                $ad_before_trying = intval($ad_match[1]);
                @ unlink(AD_DIRNAME . '/' . $ad_FName);
             }
          }
       }
       closedir($ad_dir);
       /*** Выводить или не выводить сообщение об ошибке ***/
       if ($ad_before_trying > 0)
       {
          /* Если обращение было недавно, то выводим сообщение об ошибке */
          header ('HTTP/1.0 503 Service Unavailable');
          header ('Status: 503 Service Unavailable');
          header ('Retry-After: ' . $ad_delay * 3);
    ?>
    <!doctype html public "-//W3C//DTD HTML 4.01//EN"
    "http://www.w3.org/TR/html4/strict.dtd">
    <html>
    <head>
    <title>Ошибка 503</title>
    <meta http-equiv="Content-Type" content="text/html; charset=Windows-1251" />
    </head>
    <body>
    <h1>Ошибка 503 (Service Unavailable)</h1>
    <p>Сервер не может в данный момент выдать запрашиваемую Вами страницу.
    Попробуйте вызвать эту страницу позже (клавиша F5).</p>
    </body>
    </html>
    <?php
          ad_WriteIP($ad_before_trying);   // Перед выходом записываем ip
          exit;
       }else{
          ad_WriteIP($ad_before_trying);
       }
    }
    ?>

Mavn

кстати гигабот я бы тоже заблокировал толку от него фиг зато сайт колбасит немерено. для сравнения можете глянуть статистику по просмотрам за август месяц. Например для моего сайта средняя положительная статистика по колву просмотров 20-29к просмотров в сутки, если больше, то значит либо сайт качают качалками либо какой то одаренный бот фигачит сайт. в моем случае при трафе в 300-500 мегов в сутки, превращалишь 1гиг+ а следовательно повышалась нагрузка на сервак так что советовал бы хотя бы раз в сутки а лучше пару раз в сутки мониторить сервак руками или наверное поставить все на автомат.

Хотя в моем случае можно ограничить все фаерволом или же апачем поставить на автомат банн за черезмерную активность  ;D
SimpleMachines Russian Community Team
п.1 Пройду курсы гадалок для определения исходного кода по скриншоту.

п.2 У вас нет желания читать правила раздела, у меня нет желания одобрять темы, которые не соответствуют этим правилам.

oldcopy

Yworld_garry

Спасибо, реализовал у себя на форуме, посмотрим теперь, что будет с нагрузкой на SQL. У меня это тоже больная тема.
AUT VIAM INVENIAM AUT FACIAM <или найду дорогу или проложу ее сам (лат.)>

vladok

Сорри -туплю, полночи не спал...
Как оптимальнее подключить этот скрипт к index.php?
Харе кришна, май либер зольдат...

vladok

Работает...
Однако при отправке поста из быстрого ответа мигом выкидывает в страничку ошибки, формируемую скриптом.
Я так понимаю, надо играться с параметром AD_TRYING ?
Харе кришна, май либер зольдат...