Identifikujeme potenciální „zlé“ roboty a zablokujeme je pomocí IP

Identifikujeme potenciální „zlé“ roboty a zablokujeme je pomocí IP

Dobrý den! V článku vám řeknu, jak mohou uživatelé běžného hostingu zachytit IP adresy, které generují nadměrné zatížení webu a následně je zablokovat pomocí hostingových nástrojů, bude tam „trochu“ php kódu, pár screenshotů.

Vstupní data:

  1. Web vytvořený na CMS WordPress
  2. Hosting Beget (toto není reklama, ale snímky obrazovky administračního panelu budou od tohoto konkrétního poskytovatele hostingu)
  3. Stránky WordPress byly spuštěny někde na začátku roku 2000 a mají velké množství článků a materiálů
  4. PHP verze 7.2
  5. WP má nejnovější verzi
  6. Již nějakou dobu začal web podle hostingových dat generovat vysoké zatížení MySQL. Každý den tato hodnota přesahovala 120 % normy na účet
  7. Podle Yandexu. Stránky Metrica navštíví 100-200 lidí denně

Nejprve bylo provedeno toto:

  1. Tabulky databáze byly vyčištěny od nahromaděného odpadu
  2. Byly deaktivovány nepotřebné pluginy, odstraněny části zastaralého kódu

Zároveň upozorňuji na to, že se zkoušely možnosti cachování (cachovací pluginy), dělala se pozorování - ale zátěž 120% z jednoho webu byla neměnná a mohla jen růst.

Jak vypadala přibližná zátěž na hosting databází

Identifikujeme potenciální „zlé“ roboty a zablokujeme je pomocí IP
Nahoře je dotyčný web, hned pod ním jsou další weby, které mají stejné cms a přibližně stejnou návštěvnost, ale vytvářejí menší zátěž.

Analýza

  • Bylo provedeno mnoho pokusů s možnostmi ukládání dat do mezipaměti, pozorování byla prováděna několik týdnů (naštěstí mi během této doby hosting nikdy nenapsal, že jsem na tom tak špatně a budu odpojen)
  • Došlo k analýze a hledání pomalých dotazů, poté byla mírně změněna struktura databáze a typ tabulky
  • Pro analýzu jsme primárně použili vestavěný AWStats (mimochodem pomohl vypočítat nejhorší IP adresu na základě objemu provozu
  • Metrika – metrika poskytuje informace pouze o lidech, nikoli o robotech
  • Byly pokusy použít pluginy pro WP, které dokážou filtrovat a blokovat návštěvníky i podle země umístění a různých kombinací
  • Zcela radikálním způsobem se ukázalo být uzavření webu na jeden den s poznámkou „Jsme v údržbě“ - to bylo také provedeno pomocí slavného pluginu. V tomto případě očekáváme, že zatížení klesne, ale ne na nulové hodnoty, protože ideologie WP je založena na hácích a pluginy začnou svou činnost, když dojde k háku, a než k ní dojde, mohou požadavky do databáze již být vyroben

Nápad

  1. Spočítejte si IP adresy, které odešlou mnoho požadavků v krátkém časovém období.
  2. Zaznamenejte počet přístupů na web
  3. Blokovat přístup k webu na základě počtu přístupů
  4. Blokujte pomocí položky „Odmítnout od“ v souboru .htaccess
  5. Nezvažoval jsem jiné možnosti, jako jsou iptables a pravidla pro Nginx, protože píšu o hostingu

Objevil se nápad, takže je třeba ho realizovat, jako bez tohoto...

  • Vytváření tabulek pro shromažďování dat
    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;
    
  • Vytvoříme soubor, do kterého umístíme kód. Kód se zaznamená do tabulek kandidátů na blokování a uchová historii pro ladění.

    Kód souboru pro záznam IP adres

    <?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ískat IP adresu návštěvníka a zapsat ji do tabulky. Pokud je ip již v tabulce, pole cnt se zvýší (počet požadavků na web)

  • Teď ta děsivá věc... Teď mě za mé činy upálí :)
    Abychom zaznamenali každý požadavek na web, připojíme kód souboru k hlavnímu souboru WordPress – wp-load.php. Ano, změníme soubor jádra a přesně poté, co globální proměnná $wpdb již existuje

Nyní tedy vidíme, jak často je ta či ona IP adresa označena v naší tabulce a s hrnkem kávy se tam jednou za 5 minut podíváme, abychom obrázku porozuměli

Identifikujeme potenciální „zlé“ roboty a zablokujeme je pomocí IP

Poté jednoduše zkopírujte „škodlivou“ IP, otevřete soubor .htaccess a přidejte jej na konec souboru

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

To je ono, nyní 94.242.55.248 - nemá přístup k webu a negeneruje zatížení databáze

Ale pokaždé, když kopírování ručně, jako je toto, není příliš spravedlivý úkol, a kromě toho byl kód zamýšlen jako autonomní

Pojďme přidat soubor, který bude spuštěn přes CRON každých 30 minut:

Kód souboru upravující .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 souboru je poměrně jednoduchý a primitivní a jeho hlavní myšlenkou je vzít kandidáty na blokování a zadat pravidla blokování do souboru .htaccess mezi komentáře
# start_auto_deny_list a # end_auto_deny_list

Nyní jsou „škodlivé“ IP adresy blokovány samy o sobě a soubor .htaccess vypadá 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ýsledkem je, že poté, co tento kód začne fungovat, můžete vidět výsledek na panelu hostování:

Identifikujeme potenciální „zlé“ roboty a zablokujeme je pomocí IP

PS: Materiál je autorův, i když jsem část publikoval na svém webu, na Habre jsem dostal rozšířenější verzi.

Zdroj: www.habr.com

Přidat komentář