Identifikujeme potenciálne „zlé“ roboty a blokujeme ich podľa IP

Identifikujeme potenciálne „zlé“ roboty a blokujeme ich podľa IP

Dobrý deň! V článku vám poviem, ako môžu používatelia bežného hostingu zachytiť IP adresy, ktoré generujú nadmerné zaťaženie stránky a následne ich zablokovať pomocou hostingových nástrojov, bude tam „trochu“ php kódu, pár screenshotov.

Vstupné Data:

  1. Webová stránka vytvorená na CMS WordPress
  2. Hosting Beget (toto nie je reklama, ale snímky obrazovky administračného panela budú od tohto konkrétneho poskytovateľa hostingu)
  3. Stránka WordPress bola spustená niekde začiatkom roku 2000 a má veľké množstvo článkov a materiálov
  4. PHP verzia 7.2
  5. WP má najnovšiu verziu
  6. Už nejaký čas stránka začala generovať vysokú záťaž na MySQL podľa hostingových údajov. Každý deň táto hodnota presahovala 120 % normy na účet
  7. Podľa Yandex. Stránky Metrica navštívi 100-200 ľudí denne

V prvom rade sa urobilo toto:

  1. Databázové tabuľky boli vyčistené od nahromadeného odpadu
  2. Nepotrebné pluginy boli deaktivované, sekcie zastaraného kódu boli odstránené

Zároveň dávam do pozornosti, že sa skúšali možnosti cachovania (cachovacie pluginy), robili sa pozorovania - no záťaž 120% z jednej stránky bola nezmenená a mohla len rásť.

Ako vyzeralo približné zaťaženie hostingových databáz

Identifikujeme potenciálne „zlé“ roboty a blokujeme ich podľa IP
V hornej časti je príslušná stránka, hneď pod ňou sú ďalšie stránky, ktoré majú rovnaké cms a približne rovnakú návštevnosť, ale vytvárajú menšiu záťaž.

analýza

  • Uskutočnilo sa veľa pokusov s možnosťami ukladania údajov do vyrovnávacej pamäte, pozorovania sa vykonávali niekoľko týždňov (našťastie mi počas tejto doby hosting nikdy nenapísal, že som na tom tak zle a budem odpojený)
  • Prebehla analýza a vyhľadávanie pomalých dopytov, následne sa mierne zmenila štruktúra databázy a typ tabuľky
  • Na analýzu sme primárne použili vstavaný AWStats (mimochodom, pomohol vypočítať najhoršiu IP adresu na základe objemu návštevnosti
  • Metrika – metrika poskytuje informácie iba o ľuďoch, nie o robotoch
  • Boli pokusy použiť pluginy pre WP, ktoré dokážu filtrovať a blokovať návštevníkov aj podľa krajiny umiestnenia a rôznych kombinácií
  • Úplne radikálnym spôsobom sa ukázalo byť zatvorenie stránky na jeden deň s poznámkou „Sme v údržbe“ - to bolo vykonané aj pomocou známeho doplnku. V tomto prípade očakávame, že zaťaženie klesne, ale nie na nulové hodnoty, keďže ideológia WP je založená na háčikoch a zásuvné moduly začnú svoju činnosť, keď dôjde k háčiku, a predtým, ako dôjde k háčiku, môžu požiadavky do databázy už urobené

Nápad

  1. Vypočítajte adresy IP, ktoré za krátky čas odoslali veľa žiadostí.
  2. Zaznamenajte počet prístupov na stránku
  3. Blokovať prístup na stránku na základe počtu prístupov
  4. Blokujte pomocou položky „Odmietnuť od“ v súbore .htaccess
  5. Nezvažoval som iné možnosti, ako sú iptables a pravidlá pre Nginx, pretože píšem o hostingu

Objavil sa nápad, tak ho treba zrealizovať, keďže bez tohto...

  • Vytváranie tabuliek na zhromažďovanie údajov
    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;
    
  • Vytvorme si súbor, do ktorého umiestnime kód. Kód sa zaznamená do tabuliek kandidátov na blokovanie a uchováva históriu na ladenie.

    Kód súboru na zaznamenávanie IP adries

    <?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);
    
    

    Podstatou kódu je získať IP adresu návštevníka a zapísať ju do tabuľky. Ak je ip už v tabuľke, pole cnt sa zvýši (počet požiadaviek na stránku)

  • Teraz tá strašná vec... Teraz ma za moje činy upália :)
    Aby sme zaznamenali každú požiadavku na stránku, pripojíme kód súboru k hlavnému súboru WordPress - wp-load.php. Áno, zmeníme súbor jadra a presne potom, čo globálna premenná $wpdb už existuje

Takže teraz vidíme, ako často je tá alebo oná IP adresa označená v našej tabuľke a s hrnčekom kávy sa tam raz za 5 minút pozrieme, aby sme pochopili obrázok

Identifikujeme potenciálne „zlé“ roboty a blokujeme ich podľa IP

Potom jednoducho skopírujte „škodlivú“ IP, otvorte súbor .htaccess a pridajte ho na koniec súboru

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

To je všetko, teraz 94.242.55.248 - nemá prístup na stránku a negeneruje zaťaženie databázy

Ale zakaždým, keď kopírovanie ručne, ako je toto, nie je veľmi spravodlivá úloha, a okrem toho, kód mal byť autonómny

Pridajme súbor, ktorý sa bude spúšťať cez CRON každých 30 minút:

Kód súboru upravujúci .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)));

Kód súboru je pomerne jednoduchý a primitívny a jeho hlavnou myšlienkou je zobrať kandidátov na blokovanie a zadať pravidlá blokovania do súboru .htaccess medzi komentáre
# start_auto_deny_list a # end_auto_deny_list

Teraz sú „škodlivé“ adresy IP blokované samy osebe a súbor .htaccess vyzerá asi takto:

# 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

V dôsledku toho, keď tento kód začne fungovať, môžete vidieť výsledok na paneli hosťovania:

Identifikujeme potenciálne „zlé“ roboty a blokujeme ich podľa IP

PS: Materiál je autorov, aj keď som časť zverejnil na svojom webe, dostal som rozšírenejšiu verziu na Habre.

Zdroj: hab.com

Pridať komentár