Izračunavamo potencijalne "zle" botove i blokiramo ih po IP-u

Izračunavamo potencijalne "zle" botove i blokiramo ih po IP-u

Dobar dan! U članku ću vam reći kako korisnici običnog hostinga mogu uhvatiti IP adrese koje stvaraju preveliko opterećenje na web stranici i zatim ih blokirati pomoću hosting alata, bit će "malo" php koda, nekoliko snimaka ekrana.

Ulazni podaci:

  1. Web stranica kreirana na CMS WordPress-u
  2. Hosting Beget (ovo nije reklama, ali snimci ekrana administratorskog panela će biti od ovog konkretnog hosting provajdera)
  3. WordPress stranica je pokrenuta negdje početkom 2000. godine i ima veliki broj članaka i materijala
  4. PHP verzija 7.2
  5. WP ima najnoviju verziju
  6. Već neko vrijeme, stranica je počela stvarati veliko opterećenje na MySQL prema podacima o hostingu. Svaki dan je ova vrijednost prelazila 120% norme po računu
  7. Prema Yandexu. Metricu stranicu posjeti 100-200 ljudi dnevno

Prije svega, ovo je urađeno:

  1. Tabele baze podataka su očišćene od nagomilanog smeća
  2. Nepotrebni dodaci su onemogućeni, dijelovi zastarjelog koda su uklonjeni

Istovremeno, želio bih da vam skrenem pažnju na činjenicu da su isprobane opcije keširanja (dodatci za keširanje), napravljena su zapažanja - ali opterećenje od 120% sa jedne stranice je bilo nepromijenjeno i moglo je samo rasti.

Kako je izgledalo približno opterećenje hosting baza podataka

Izračunavamo potencijalne "zle" botove i blokiramo ih po IP-u
Na vrhu je dotični sajt, odmah ispod su drugi sajtovi koji imaju isti cms i približno isti promet, ali stvaraju manje opterećenje.

Анализ

  • Učinjeno je mnogo pokušaja sa opcijama keširanja podataka, zapažanja su vršena tokom nekoliko sedmica (srećom, za to vrijeme mi hosting nikada nije napisao da sam tako loš i da ću biti isključen)
  • Izvršena je analiza i traženje sporih upita, zatim su struktura baze podataka i tip tabele malo promijenjeni
  • Za analizu smo prvenstveno koristili ugrađeni AWStats (usput, pomogao je da se izračuna najgora IP adresa na osnovu obima saobraćaja
  • Metrik - metrika pruža informacije samo o ljudima, ne i o botovima
  • Bilo je pokušaja korištenja dodataka za WP koji mogu filtrirati i blokirati posjetitelje čak i prema zemlji lokacije i raznim kombinacijama
  • Ispostavilo se da je potpuno radikalan način zatvaranje stranice na jedan dan uz napomenu "U tijeku je održavanje" - to je također učinjeno pomoću poznatog dodatka. U ovom slučaju očekujemo da će opterećenje pasti, ali ne na nulte vrijednosti, budući da je WP ideologija zasnovana na zakačanjima i dodaci započinju svoju aktivnost kada dođe do „hook“-a, a prije nego što se „hook“ dogodi, zahtjevi prema bazi podataka mogu već biti napravljen

Ideja

  1. Izračunajte IP adrese koje upućuju mnogo zahtjeva u kratkom vremenskom periodu.
  2. Zabilježite broj posjeta stranici
  3. Blokirajte pristup sajtu na osnovu broja poseta
  4. Blokirajte pomoću unosa “Deny from” u datoteci .htaccess
  5. Nisam razmatrao druge opcije, kao što su iptables i pravila za Nginx, jer pišem o hostingu

Pojavila se ideja pa je treba realizovati, jer bez ovoga...

  • Kreiranje tabela za prikupljanje podataka
    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;
    
  • Kreirajmo datoteku u koju ćemo smjestiti kod. Kôd će snimati u tabele kandidata za blokiranje i čuvati historiju za otklanjanje grešaka.

    Kôd datoteke za snimanje IP adresa

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

    Suština koda je da dobijete IP adresu posjetitelja i upišete je u tabelu. Ako je ip već u tabeli, polje cnt će biti povećano (broj zahtjeva na stranicu)

  • E sad ono strašno... Sad će me spaliti zbog mojih postupaka :)
    Da bismo snimili svaki zahtjev na stranicu, povezujemo kod datoteke sa glavnom WordPress datotekom - wp-load.php. Da, mijenjamo kernel fajl i upravo nakon što globalna varijabla $wpdb već postoji

Dakle, sada možemo vidjeti koliko često je ova ili ona IP adresa označena u našoj tabeli i uz šoljicu kafe pogledamo tamo jednom svakih 5 minuta da shvatimo sliku

Izračunavamo potencijalne "zle" botove i blokiramo ih po IP-u

Zatim jednostavno kopirajte "štetnu" IP adresu, otvorite .htaccess datoteku i dodajte je na kraj datoteke

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

To je to, sada 94.242.55.248 - nema pristup stranici i ne stvara opterećenje baze podataka

Ali svaki put ovako ručno kopiranje nije baš ispravan zadatak, a osim toga, kod je trebao biti autonoman

Dodajmo fajl koji će se izvršavati preko CRON-a svakih 30 minuta:

Kod fajla koji mijenja .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)));

Kod fajla je prilično jednostavan i primitivan i njegova glavna ideja je da uzme kandidate za blokiranje i unese pravila blokiranja u .htaccess fajl između komentara
# start_auto_deny_list i # end_auto_deny_list

Sada su "štetne" IP adrese same po sebi blokirane, a .htaccess datoteka izgleda otprilike ovako:

# 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

Kao rezultat toga, nakon što ovaj kod počne raditi, možete vidjeti rezultat na hosting panelu:

Izračunavamo potencijalne "zle" botove i blokiramo ih po IP-u

PS: Materijal je autorov, iako sam dio objavio na svojoj web stranici, dobio sam proširenu verziju na Habreu.

izvor: www.habr.com

Dodajte komentar