Вылічаем патэнцыйных "злых" ботаў і блакуем іх па IP

Вылічаем патэнцыйных "злых" ботаў і блакуем іх па IP

Добрага дня! У артыкуле распавяду як мага карыстачам звычайнага хостынгу адлавіць IP адрасы генеравальныя залішнюю нагрузку на сайт і затым блакаваць іх пры дапамозе сродкаў хостынгу, будзе «крыху» php кода, некалькі скрыншотаў.

Уступныя дадзеныя:

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

У першую чаргу было зроблена:

  1. Вычышчаны табліцы БД ад назапашанага смецця
  2. Адключаныя не патрэбныя плагіны, прыбраныя ўчасткі састарэлага кода

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

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

Вылічаем патэнцыйных "злых" ботаў і блакуем іх па IP
У топе знаходзіцца сайт аб якім ідзе гаворка, крыху ніжэй іншыя сайты якія маюць тую ж 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

Далей проста, скапіявалі "шкодны" 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

У выніку пасля пачатку дзеяння такога кода можна ўбачыць вынік у хостынг панэлі:

Вылічаем патэнцыйных "злых" ботаў і блакуем іх па IP

PS: Матэрыял аўтарскі, хоць яго частка я і публікаваў на сваім сайце, але на Habre атрымалася больш пашыраная версія.

Крыніца: habr.com

Дадаць каментар