Разработчику. Сборник рецептов PHP
Задавайте вопросы

Программа: Контролер злоумышленных
пользователей

Вернуться назад

Скорость работы разделяемой памяти делает ее идеальным выбором для хранения данных, к которым необходим частый доступ со стороны различных процессов веб-сервера, когда файлы или база данных работают слишком медленно. В Примере pc_Web_Abuse_Check class показан класс pc_Web_Abuse_Check, использующий разделяемую память для отслеживания соединений с веб-сайтом, для того чтобы отсечь пользователей, которые злоупотребляют веб-сайтом, бомбардируя его запросами.

Пример pc_Web_Abuse_Check class

class pc_Web_Abuse_Check {
     var $sem_key;
     var $shm_key;
     var $shm_size;
     var $recalc_seconds;
     var $pageview_threshold;
     var $sem;
     var $shm;
     var $data;
     var $exclude;
     var $block_message;

     function pc_Web_Abuse_Check() {
          $this -> sem_key = 5000;
          $this -> shm_key = 5001;
          $this -> shm_size = 16000;
          $this -> recalc_seconds = 60;
          $this -> pageview_threshold = 30;

          $this -> exclude['/ok-to-bombard.html'] = 1;
          $this -> block_message =<<<END

     <html>
     <head><title>403 Forbidden</title></head>
     <body>
     <h1>Forbidden</h1>
     You have been blocked from retrieving pages from this site due to
     abusive repetitive activity from your account. If you believe this
     is an error, please contact
     <a href="mailto:webmaster@example.com?subject=Site+Abuse"
     >webmaster@example.com</a>.
     </body>
     </html>
     END;

          }

     function get_lock( ) {
          $this -> sem = sem_get($this -> sem_key,1,0600);
          if (sem_acquire($this -> sem)) {
          $this -> shm = shm_attach($this -> shm_key,$this -> shm_size,0600);
          $this -> data = shm_get_var($this -> shm,'data');
     } else {
          error_log("Can't acquire semaphore $this -> sem_key");
     }
}

     function release_lock( ) {
          if (isset($this -> data)) {
               shm_put_var($this -> shm,'data',$this -> data);
               }
               shm_detach($this -> shm);
               sem_release($this -> sem);
     }

function check_abuse($user) {
     $this -> get_lock( );
     if ($this -> data['abusive_users'][$user]) {
          // если пользователь находится в списке, освобождаем семафор и память
          $this -> release_lock( );
          // выводим страницу"you are blocked"
          header('HTTP/1.0 403 Forbidden');
          print $this -> block_message;
          return true;
     } else {

     // фиксируем пользователя, находящегося на странице в настоящий момент
     $now = time( );
     if (! $this -> exclude[$_SERVER['PHP_SELF']]) {
          $this -> data['user_traffic'][$user]++;
}

// (иногда) идем в начало списка и добавляем плохих людей
if (! $this -> data['traffic_start']) {
     $this -> data['traffic_start'] = $now;
} else {
     if (($now - $this -> data['traffic_start']) > $this ->recalc_seconds) {
          while (list($k,$v) = each($this-> data['user_traffic'])) {
               if ($v > $this -> pageview_threshold) {
               $this -> data['abusive_users'][$k] = $v;
               // регистрируем добавление пользователя
               // в список злоумышленных пользователей
               error_log("Abuse: [$k] (from ".$_SERVER['REMOTE_ADDR'].')');
          }
     }
     $this -> data['traffic_start'] = $now;
     $this -> data['user_traffic'] = array();
     }
}
$this -> release_lock( );
}
return false;
}
}

Для того чтобы с этим классом можно было работать, в начале страницы вызовите метод check_abuse(), передав ему имя зарегистрированного пользователя:

// get_logged_in_user_name() – это функция, которая определяет,
// зарегистрировался ли пользователь
if ($user = get_logged_in_user_name()) {
     $abuse = new pc_Web_Abuse_Check();
     if ($abuse -> check_abuse($user)) {
          exit;
     }
}

Метод check_abuse() защищает исключительный доступ к сегменту разделяемой памяти, в котором хранится информация о пользователях и трафике, записанная туда с помощью метода get_lock(). Если пользователь уже находится в списке злоумышленных пользователей, то метод освобождает блок в разделяемой памяти, выдает пользователю страницу ошибки и возвращает значение true. Страница ошибки определяется в конструкторе класса.

Если пользователя нет в списке злоумышленных пользователей и текущая страница (сохраненная в элементе $_SERVER['PHP_SELF']) не входит в список страниц, не подлежащих проверке на злоупотребление, то значение счетчика страниц, просмотренных пользователем, увеличивается. Список страниц, не подлежащих проверке, также определяется в конструкторе. Вызывая метод check_abuse() в начале каждой страницы и помещая страницы, которые не считаются потенциально подверженными злоупотреблению, в массив $exclude, вы тем самым обеспечиваете, что злоумышленный пользователь увидит страницу ошибки даже при просмотре страницы, которая не учитывается при подсчете злоупотреблений. Это делает поведение сайта более уравновешенным.

Следующий раздел функции check_abuse() отвечает за добавление пользователей в список злоумышленных пользователей. Если прошло более $this -> recalc_seconds секунд с момента последнего добавления пользователей в список злоумышленных пользователей, то метод смотрит на счетчики посещения страниц каждого пользователя, и если какой-либо из них превышает значение $this -> pageview_threshold,то они добавляются в список злоумышленных пользователей, а в журнал ошибок помещается сообщение. Код, который устанавливает $this -> data['traffic_start'], если он еще не установлен, выполняется только при самом первом вызове функции check_abuse(). После добавления нового злоумышленного пользователя функция check_abuse() сбрасывает счетчик пользователей и счетчик просмотра страниц и стартует новый интервал до момента следующего обновления списка злоумышленных пользователей. После освобождения блокировки разделяемой памяти он возвращает false.

Вся информация, необходимая функции check_abuse() для проведения вычислений, т. е. список злоумышленных пользователей, последние значения счетчика просмотренных страниц и время последнего определения злоумышленных пользователей, запоминается в единственном ассоциативном массиве data. Это делает чтение и запись в разделяемую память более легкой по сравнению с хранением информации в отдельных переменных, поскольку требуется только один вызов функции shm_get_var() и один вызов функции shm_put_var().

Класс pc_Web_Abuse_Check блокирует злоумышленных пользователей, но не имеет никакой системы отчетности и не позволяет добавлять в список или удалять из списка отдельных пользователей. Пример abuse-manage.php содержит программу abuse-manage.php, позволяющую манипулировать данными злоумышленных пользователей.

Пример abuse-manage.php

// класс pc_Web_Abuse_Check определен в abuse-check.php
require 'abuse-check.php';

$abuse = new pc_Web_Abuse_Check();
$now = time();

// обрабатываем команды, если они есть
$abuse -> get_lock();
switch ($_REQUEST['cmd']) {
     case 'clear':
          $abuse -> data['traffic_start'] = 0;
          $abuse -> data['abusive_users'] = array();
          $abuse -> data['user_traffic'] = array();
          break;
     case 'add':
          $abuse -> data['abusive_users'][$_REQUEST['user']] =
                    'web @ '.strftime('%c',$now);
          break;
     case 'remove':
          $abuse -> data['abusive_users'][$_REQUEST['user']] = 0;
          break;

}

$abuse -> release_lock();

// теперь значимая информация находится в $abuse -> data

print 'It is now <b>'.strftime('%c',$now).'</b><br>';
print 'Current interval started at <b>'.strftime('%c',
$abuse -> data['traffic_start']);
print '</b> ('.($now - $abuse -> data['traffic_start']).' seconds ago).<p>';

print 'Traffic in the current interval:<br>';
if (count($abuse -> data['user_traffic'])) {
     print '<table border="1"><tr><th>User</th><th>Pages</th></tr>';
     while (list($user,$pages) = each($abuse -> data['user_traffic'])) {
          print "<tr><td>$user</td><td>$pages</td></tr>";
     }
     print "</table>";
} else {
     print "<i>No traffic.</i>";
     }
          print '<p>Abusive Users:';

     if ($abuse -> data['abusive_users']) {
          print '<table border="1"><tr><th>User</th><th>Pages</th></tr>';
          while (list($user,$pages) = each($abuse -> data['abusive_users'])) {
     if (0 === $pages) {
          $pages = 'Removed';
          $remove_command = '';
     } else {
     $remove_command =
          "<a href=\"$_SERVER[PHP_SELF]?cmd=remove&user=".urlencode($user)."\
                    ">remove</a>";
}
     print "<tr><td>$user</td><td>$pages</td><td>$remove_command</td></tr>";
}
     print '</table>';
} else {
print "<i>No abusive users.</i>";
}

print<<<END
<form method="post" action="$_SERVER[PHP_SELF]">
<input type="hidden" name="cmd" value="add">
Add this user to the abusive users list:
<input type="text" name="user" value="">
<br> <input type="submit" value="Add User">
</form>
<hr>
<form method="post" action="$_SERVER[PHP_SELF]">
<input type="hidden" name="cmd" value="clear">
<input type="submit" value="Clear the abusive users list">
END;

Пример abuse-manage.php выводит информацию о текущем значении счетчика количества посещений страницы пользователем и текущий список злоумышленных пользователей, как показано на рисунке. Он также позволяет добавлять в список или удалять из списка определенных пользователей и очищать весь список.

При удалении пользователей из списка вместо вызова:

unset($abuse -> data['abusive_users'][$_REQUEST['user']])

он устанавливает следующую переменную в 0:

$abuse -> data['abusive_users'][$_REQUEST['user']]

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

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

Программа: Контролер злоумышленных пользователей

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

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

Вернуться назад

Рейтинг@Mail.ru

Яндекс.Метрика

Индекс цитирования


Рейтинг Сайтов ДОСКИ.РУ