Nous identifions les robots « maléfiques » potentiels et les bloquons par IP

Nous identifions les robots « maléfiques » potentiels et les bloquons par IP

Bonne journée! Dans l'article je vais vous expliquer comment les utilisateurs d'un hébergement classique peuvent capter les adresses IP qui génèrent une charge excessive sur le site puis les bloquer à l'aide d'outils d'hébergement, il y aura « un peu » de code php, quelques captures d'écran.

Des données d'entrée:

  1. Site créé sur le CMS WordPress
  2. Hosting Beget (ce n'est pas une publicité, mais les captures d'écran du panneau d'administration proviendront de ce fournisseur d'hébergement particulier)
  3. Le site WordPress a été lancé au début des années 2000 et contient un grand nombre d'articles et de supports.
  4. PHP version 7.2
  5. WP a la dernière version
  6. Depuis quelques temps, le site commençait à générer une charge élevée sur MySQL en fonction des données d'hébergement. Chaque jour, cette valeur dépassait 120 % de la norme par compte
  7. Selon Yandex. Le site Metrica est visité par 100 à 200 personnes par jour

Tout d'abord, ceci a été fait :

  1. Les tables de base de données ont été débarrassées des déchets accumulés
  2. Les plugins inutiles ont été désactivés, les sections de code obsolète ont été supprimées

Dans le même temps, je voudrais attirer votre attention sur le fait que des options de mise en cache (plugins de mise en cache) ont été essayées, des observations ont été faites - mais la charge de 120 % d'un site est restée inchangée et ne pouvait qu'augmenter.

À quoi ressemblait la charge approximative sur l'hébergement des bases de données

Nous identifions les robots « maléfiques » potentiels et les bloquons par IP
En haut se trouve le site en question, juste en dessous se trouvent d'autres sites qui ont les mêmes cms et à peu près le même trafic, mais créent moins de charge.

Analyse

  • De nombreuses tentatives ont été faites avec les options de mise en cache des données, des observations ont été réalisées sur plusieurs semaines (heureusement, pendant ce temps l'hébergement ne m'a jamais écrit que j'allais si mal et que je serais déconnecté)
  • Il y a eu une analyse et une recherche des requêtes lentes, puis la structure de la base de données et le type de table ont été légèrement modifiés
  • Pour l'analyse, nous avons principalement utilisé les AWStats intégrés (d'ailleurs, cela a aidé à calculer la pire adresse IP en fonction du volume de trafic
  • Métrique : la métrique fournit des informations uniquement sur les personnes, pas sur les robots.
  • Il y a eu des tentatives pour utiliser des plugins pour WP qui peuvent filtrer et bloquer les visiteurs même par pays de localisation et diverses combinaisons.
  • Une manière tout à fait radicale s'est avérée être de fermer le site pendant une journée avec la mention « Nous sommes en maintenance » - cela a également été fait à l'aide du célèbre plugin. Dans ce cas, nous nous attendons à ce que la charge chute, mais pas à des valeurs nulles, puisque l'idéologie WP est basée sur des hooks et que les plugins commencent leur activité lorsqu'un « hook » se produit, et avant que le « hook » ne se produise, les requêtes à la base de données peuvent déjà être fait

Idée

  1. Calculez les adresses IP qui font beaucoup de requêtes en peu de temps.
  2. Enregistrez le nombre de visites sur le site
  3. Bloquer l'accès au site en fonction du nombre de visites
  4. Bloquer en utilisant l'entrée « Refuser de » dans le fichier .htaccess
  5. Je n'ai pas envisagé d'autres options, comme iptables et les règles pour Nginx, car j'écris sur l'hébergement.

Une idée est apparue, il faut donc la mettre en œuvre, car sans cela...

  • Créer des tables pour accumuler des données
    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;
    
  • Créons un fichier dans lequel nous placerons le code. Le code sera enregistré dans les tables de candidats bloquants et conservera un historique pour le débogage.

    Code de fichier pour l'enregistrement des adresses 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);
    
    

    L'essence du code est d'obtenir l'adresse IP du visiteur et de l'écrire dans un tableau. Si l'ip est déjà dans le tableau, le champ cnt sera augmenté (le nombre de requêtes sur le site)

  • Maintenant, ce qui fait peur... Maintenant, ils vont me brûler pour mes actes :)
    Pour enregistrer chaque requête sur le site, nous connectons le code du fichier au fichier WordPress principal - wp-load.php. Oui, on change le fichier noyau et justement après que la variable globale $wpdb existe déjà

Ainsi, maintenant nous pouvons voir à quelle fréquence telle ou telle adresse IP est marquée dans notre tableau et avec une tasse de café nous y regardons une fois toutes les 5 minutes pour comprendre l'image

Nous identifions les robots « maléfiques » potentiels et les bloquons par IP

Copiez ensuite simplement l’IP « nuisible », ouvrez le fichier .htaccess et ajoutez-le à la fin du fichier

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

Ça y est, maintenant 94.242.55.248 - n'a pas accès au site et ne génère pas de charge sur la base de données

Mais à chaque fois copier à la main comme ça n'est pas une tâche très juste, et d'ailleurs le code était censé être autonome

Ajoutons un fichier qui sera exécuté via CRON toutes les 30 minutes :

Modification du code du fichier .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)));

Le code du fichier est assez simple et primitif et son idée principale est de prendre les candidats au blocage et de saisir les règles de blocage dans le fichier .htaccess entre les commentaires.
# start_auto_deny_list et # end_auto_deny_list

Désormais, les adresses IP « nuisibles » sont bloquées d'elles-mêmes, et le fichier .htaccess ressemble à ceci :

# 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

Par conséquent, une fois que ce code commence à fonctionner, vous pouvez voir le résultat dans le panneau d'hébergement :

Nous identifions les robots « maléfiques » potentiels et les bloquons par IP

PS : Le matériel appartient à l'auteur, même si j'en ai publié une partie sur mon site Internet, j'ai obtenu une version plus étendue sur Habré.

Source: habr.com

Ajouter un commentaire