हम संभावित "दुष्ट" बॉट्स की पहचान करते हैं और उन्हें आईपी द्वारा ब्लॉक करते हैं

हम संभावित "दुष्ट" बॉट्स की पहचान करते हैं और उन्हें आईपी द्वारा ब्लॉक करते हैं

Доброго дня! В статье расскажу как можно пользователям обычного хостинга отловить IP адреса генерирующие излишнюю нагрузку на сайт и затем блокировать их при помощи средств хостинга, будет «чуть-чуть» php кода, несколько скриншотов.

Вводные данные:

  1. Сайт созданный на CMS WordPress
  2. Хостинг Бегет (это не реклама, но скрины админки будут именно этого хостинг провайдера)
  3. WordPress сайт запущен где то в начале 2000 и имеет большое количество статей и материалов
  4. Версия PHP 7.2
  5. WP имеет последнюю версию
  6. С некоторых пор сайт начал генерировать высокую нагрузку на MySQL по данным хостинга. Каждый день это значение превышало 120% от нормы на учётную запись
  7. По данным Яндекс. Метрика сайт посещает 100-200 человек в сутки

В первую очередь было сделано:

  1. Очищены таблицы БД от накопившегося мусора
  2. Отключены не нужные плагины, убраны участки устаревшего кода

При этом обращаю внимание, пробовались варианты кеширования(плагины кеширования), проводились наблюдения — но нагрузка в 120% от одного сайта была неизменна и могла только расти.

То как выглядела примерная нагрузка по базам данных хостинга

हम संभावित "दुष्ट" बॉट्स की पहचान करते हैं और उन्हें आईपी द्वारा ब्लॉक करते हैं
В топе находится сайт о котором идёт речь, чуть ниже другие сайты которые имеют ту же cms и примерно такую же посещаемость, но создают меньше нагрузки.

के विश्लेषण

  • Было предпринято много попыток с вариантами кеширования данных, проводились наблюдения в течении нескольких недель (благо хостинг за это время мне ни разу не написал что я такой плохой и меня отключат)
  • Был анализ и поиск медленных запросов, затем была немного изменена структура БД и тип таблиц
  • Для анализа в первую очередь использовался встроенный AWStats (он кстати помог вычислить самый злой IP адрес по объему трафика
  • Метрика — метрика даёт информацию только о людях, а не о ботах
  • Были попытки использовать плагины для WP, которые умеют фильтровать и блокировать посетителей даже по стране нахождения и по различным комбинациям
  • Совсем радикальный способ оказался закрыть сайт на сутки с пометкой «Мы на техническом обслуживании» — это было так же сделано при помощи знаменитого плагина. В этом случае нагрузка ожидаем упала, но не до 0-левых значений, так как идеология WP базируется на хуках и плагины начинают свою активность при наступлении какого — либо «хука», а до наступления «хука» могут быть уже сделаны запросы к БД

विचार

  1. Вычислить IP адреса которые делают много запросов за короткий промежуток времени.
  2. Зафиксировать количество обращений к сайту
  3. На основе количества обращений блокировать доступ к сайту
  4. Блокировать при помощи записи «Deny from» в файле .htaccess
  5. Другие варианты, вроде iptables и правил для Nginx не рассматривая, ибо пишу про хостинг

Появилась идея, значит надо реализовывать, как без этого…

  • Создаём таблицы для накопления данных
    CREATE TABLE `wp_visiters_bot` (
    	`id` INT(11) NOT NULL AUTO_INCREMENT,
    	`ip` VARCHAR(300) NULL DEFAULT NULL,
    	`browser` VARCHAR(500) NULL DEFAULT NULL,
    	`cnt` INT(11) NULL DEFAULT NULL,
    	`request` TEXT NULL,
    	`input` TEXT NULL,
    	`data_update` DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    	PRIMARY KEY (`id`),
    	UNIQUE INDEX `ip` (`ip`)
    )
    COMMENT='Кандидаты для блокировки'
    COLLATE='utf8_general_ci'
    ENGINE=InnoDB
    AUTO_INCREMENT=1;
    

    CREATE TABLE `wp_visiters_bot_blocked` (
    	`id` INT(11) NOT NULL AUTO_INCREMENT,
    	`ip` VARCHAR(300) NOT NULL,
    	`data_update` DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    	PRIMARY KEY (`id`),
    	UNIQUE INDEX `ip` (`ip`)
    )
    COMMENT='Список уже заблокированных'
    COLLATE='utf8_general_ci'
    ENGINE=InnoDB
    AUTO_INCREMENT=59;
    

    CREATE TABLE `wp_visiters_bot_history` (
    	`id` INT(11) NOT NULL AUTO_INCREMENT,
    	`ip` VARCHAR(300) NULL DEFAULT NULL,
    	`browser` VARCHAR(500) NULL DEFAULT NULL,
    	`cnt` INT(11) NULL DEFAULT NULL,
    	`data_update` DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    	`data_add` DATETIME NULL DEFAULT CURRENT_TIMESTAMP,
    	PRIMARY KEY (`id`),
    	UNIQUE INDEX `ip` (`ip`)
    )
    COMMENT='История всех запросов для дебага'
    COLLATE='utf8_general_ci'
    ENGINE=InnoDB
    AUTO_INCREMENT=1;
    
  • Создадим файл в который поместим код. Код будет запись в таблицы кандидатов на блокировку и вести историю для дебага.

    Код файла, для записи IP адресов

    <?php
    
    if (!defined('ABSPATH')) {
        return;
    }
    
    global $wpdb;
    
    /**
     * Вернёт конкретный IP адрес посетителя
     * @return boolean
     */
    function coderun_get_user_ip() {
    
        $client_ip = '';
    
        $address_headers = array(
            'HTTP_CLIENT_IP',
            'HTTP_X_FORWARDED_FOR',
            'HTTP_X_FORWARDED',
            'HTTP_X_CLUSTER_CLIENT_IP',
            'HTTP_FORWARDED_FOR',
            'HTTP_FORWARDED',
            'REMOTE_ADDR',
        );
    
        foreach ($address_headers as $header) {
            if (array_key_exists($header, $_SERVER)) {
    
                $address_chain = explode(',', $_SERVER[$header]);
                $client_ip = trim($address_chain[0]);
    
                break;
            }
        }
    
        if (!$client_ip) {
            return '';
        }
    
    
        if ('0.0.0.0' === $client_ip || '::' === $client_ip || $client_ip == 'unknown') {
            return '';
        }
    
        return $client_ip;
    }
    
    $ip = esc_sql(coderun_get_user_ip()); // IP адрес посетителя
    
    if (empty($ip)) {// Нет IP, ну и идите лесом...
        header('Content-type: application/json;');
        die('Big big bolt....');
    }
    
    $browser = esc_sql($_SERVER['HTTP_USER_AGENT']); //Данные для анализа браузера
    
    $request = esc_sql(wp_json_encode($_REQUEST)); //Последний запрос который был к сайту
    
    $input = esc_sql(file_get_contents('php://input')); //Тело запроса, если было
    
    $cnt = 1;
    
    //Запрос в основную таблицу с временными кондидатами на блокировку
    $query = <<<EOT
        INSERT INTO wp_visiters_bot (`ip`,`browser`,`cnt`,`request`,`input`)
            VALUES  ('{$ip}','{$browser}','{$cnt}','{$request}','$input')
             ON DUPLICATE KEY UPDATE cnt=cnt+1,request=VALUES(request),input=VALUES(input),browser=VALUES(browser)
    EOT;
    
    //Запрос для истории
    $query2 = <<<EOT
        INSERT INTO wp_visiters_bot_history (`ip`,`browser`,`cnt`)
            VALUES  ('{$ip}','{$browser}','{$cnt}')
             ON DUPLICATE KEY UPDATE cnt=cnt+1,browser=VALUES(browser)
    EOT;
    
    
    $wpdb->query($query);
    
    $wpdb->query($query2);
    
    

    Суть кода в том что бы получить IP адрес посетителя и записать его в таблицу. Если ip уже есть в таблице, будет произведено увеличение поля cnt (количество запросов к сайту)

  • Теперь страшное… Сейчас меня сожгут за мои действиия 🙂
    Что бы записывать каждое обращение сайту, подключаем код файла в главный файл WordPress — wp-load.php. Да именно изменяем файл ядра и именно после того как уже существует глобальная переменная $wpdb

Итак, теперь мы можем видеть как часто тот или иной IP адрес отмечается у нас в таблице и с кружкой кофе заглядываем туда раз в 5-ь минут для понимания картины

हम संभावित "दुष्ट" बॉट्स की पहचान करते हैं और उन्हें आईपी द्वारा ब्लॉक करते हैं

Дальше просто, скопировали «вредный» IP, открыли файл .htaccess и добавили в конец файла

Order allow,deny
Allow from all
# start_auto_deny_list
Deny from 94.242.55.248
# end_auto_deny_list

Всё, теперь 94.242.55.248 — не имеет доступа к сайту и не генерирует нагрузку на БД

Но каждый раз так руками копировать не очень праведное занятие, да и к тому же код задумывался как автономный

Добавим файл, который будет исполняться по CRON каждые 30 минут:

Код файла модифицирующий .htaccess

<?php

/**
 * Файл автоматического задания блокировок по IP адресу
 * Должен запрашиваться через CRON
 */
if (empty($_REQUEST['key'])) {
    die('Hello');
}

require('wp-load.php');

global $wpdb;

$limit_cnt = 70; //Лимит запросов по которым отбирать

$deny_table = $wpdb->get_results("SELECT * FROM wp_visiters_bot WHERE cnt>{$limit_cnt}");

$new_blocked = [];

$exclude_ip = [
    '87.236.16.70'//адрес хостинга
];

foreach ($deny_table as $result) {

    if (in_array($result->ip, $exclude_ip)) {
        continue;
    }

    $wpdb->insert('wp_visiters_bot_blocked', ['ip' => $result->ip], ['%s']);
}

$deny_table_blocked = $wpdb->get_results("SELECT * FROM wp_visiters_bot_blocked");

foreach ($deny_table_blocked as $blocked) {
    $new_blocked[] = $blocked->ip;
}

//Очистка таблицы
$wpdb->query("DELETE FROM wp_visiters_bot");

//echo '<pre>';print_r($new_blocked);echo '</pre>';

$file = '.htaccess';

$start_searche_tag = 'start_auto_deny_list';

$end_searche_tag = 'end_auto_deny_list';

$handle = @fopen($file, "r");
if ($handle) {

    $replace_string = '';//Тест для вставки в файл .htaccess

    $target_content = false; //Флаг нужного нам участка кода

    while (($buffer = fgets($handle, 4096)) !== false) {

        if (stripos($buffer, 'start_auto_deny_list') !== false) {
            $target_content = true;
            continue;
        }

        if (stripos($buffer, 'end_auto_deny_list') !== false) {
            $target_content = false;

            continue;
        }

        if ($target_content) {
            $replace_string .= $buffer;
        }
    }
    if (!feof($handle)) {
        echo "Ошибка: fgets() неожиданно потерпел неудачуn";
    }
    fclose($handle);
}

//Текущий файл .htaccess
$content = file_get_contents($file);

$content = str_replace($replace_string, '', $content);

//Очищаем все блокировки в файле .htaccess
file_put_contents($file, $content);

//Запись новых блокировок
$str = "# {$start_searche_tag}" . PHP_EOL;

foreach ($new_blocked as $key => $value) {
    $str .= "Deny from {$value}" . PHP_EOL;
}

file_put_contents($file, str_replace("# {$start_searche_tag}", $str, file_get_contents($file)));

Код файла достаточно прост и примитивен и главная его идея в том что бы взять кандидатов на блокировку и вписать правила блокирвоки в файл .htaccess между комментариями
# start_auto_deny_list и # end_auto_deny_list

Теперь «вредные» ip блокируются сами собой, а файл .htaccess выглядит примерно так:

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>

# END WordPress

Order allow,deny
Allow from all

# start_auto_deny_list
Deny from 94.242.55.248
Deny from 207.46.13.122
Deny from 66.249.64.164
Deny from 54.209.162.70
Deny from 40.77.167.86
Deny from 54.146.43.69
Deny from 207.46.13.168
....... ниже другие адреса
# end_auto_deny_list

В итоге после начала действия такого кода можно увидеть результат в хостинг панели:

हम संभावित "दुष्ट" बॉट्स की पहचान करते हैं और उन्हें आईपी द्वारा ब्लॉक करते हैं

PS: Материал авторский, хоть его часть я и публиковал на своём сайте но на Habre получилась более расширенная версия.

स्रोत: www.habr.com

एक टिप्पणी जोड़ें