Identificiramo potencijalne "zle" robote i blokiramo ih prema IP-u

Identificiramo potencijalne "zle" robote i blokiramo ih prema IP-u

Dobar dan! U članku ću vam reći kako korisnici redovnog hostinga mogu uhvatiti IP adrese koje generiraju prekomjerno opterećenje na stranici i zatim ih blokirati pomoću alata za hosting, bit će "malo" php koda, nekoliko screenshotova.

Ulazni podaci:

  1. Web stranica izrađena na CMS WordPress
  2. Hosting Beget (ovo nije reklama, ali snimke zaslona administratorske ploče bit će od ovog određenog pružatelja usluga hostinga)
  3. WordPress stranica pokrenuta je 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 generirati veliko opterećenje na MySQL prema podacima hostinga. Svaki dan ta je vrijednost prelazila 120% norme po računu
  7. Prema Yandexu. Stranica Metrica dnevno posjeti 100-200 ljudi

Prije svega, ovo je učinjeno:

  1. Tablice baze podataka očišćene su od nakupljenog smeća
  2. Onemogućeni su nepotrebni dodaci, uklonjeni su dijelovi zastarjelog koda

U isto vrijeme, želio bih vam skrenuti pozornost na činjenicu da su opcije predmemoriranja (dodaci za predmemoriranje) isprobane, opažanja su napravljena - ali opterećenje od 120% s jedne stranice ostalo je nepromijenjeno i moglo je samo rasti.

Kako je otprilike izgledalo opterećenje hosting baza podataka

Identificiramo potencijalne "zle" robote i blokiramo ih prema IP-u
Na vrhu je dotična stranica, odmah ispod su druge stranice koje imaju isti cms i približno isti promet, ali stvaraju manje opterećenja.

Analiza

  • Učinjeni su mnogi pokušaji s opcijama predmemoriranja podataka, promatranja su provedena tijekom nekoliko tjedana (srećom, tijekom tog vremena hosting mi nikada nije napisao da sam tako loš i da ću biti isključen)
  • Obavljena je analiza i traženje sporih upita, zatim je malo promijenjena struktura baze podataka i tip tablice
  • Za analizu smo prvenstveno koristili ugrađeni AWStats (usput, pomogao je izračunati najlošiju IP adresu na temelju količine prometa
  • Metrika - 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 zatvoriti stranicu na jedan dan s napomenom "Mi smo pod održavanjem" - 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 se WP ideologija temelji na zakačkama, a dodaci počinju svoju aktivnost kada se dogodi "zakačica", a prije nego što se zakačica dogodi, zahtjevi prema bazi podataka mogu već biti napravljeno

Ideja

  1. Izračunajte IP adrese koje čine puno zahtjeva u kratkom vremenskom razdoblju.
  2. Zabilježite broj posjeta stranici
  3. Blokirajte pristup stranici na temelju broja posjeta
  4. Blokirajte pomoću unosa "Deny from" u datoteci .htaccess
  5. Nisam razmatrao druge opcije, poput iptables i pravila za Nginx, jer pišem o hostingu

Pojavila se ideja, pa je treba provesti, kako bez ovoga...

  • Izrada tablica 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. Kod će se zabilježiti u tablicama kandidata za blokiranje i čuvati povijest za otklanjanje pogrešaka.

    Kod 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 dobiti IP adresu posjetitelja i upisati je u tablicu. Ako je ip već u tablici, polje cnt će se povećati (broj zahtjeva za stranicu)

  • Sad ono strašno... Sad će me spaliti zbog mojih djela :)
    Za snimanje svakog zahtjeva na stranicu, povezujemo kod datoteke s glavnom WordPress datotekom - wp-load.php. Da, mijenjamo datoteku kernela i to točno nakon što globalna varijabla $wpdb već postoji

Dakle, sada možemo vidjeti koliko je često ova ili ona IP adresa označena u našoj tablici i sa šalicom kave pogledamo tamo svakih 5 minuta kako bismo razumjeli sliku

Identificiramo potencijalne "zle" robote i blokiramo ih prema IP-u

Zatim jednostavno kopirajte “štetni” IP, 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 generira opterećenje baze podataka

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

Dodajmo datoteku koja će se izvršavati preko CRON-a svakih 30 minuta:

Datotečni kod 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 datoteke je prilično jednostavan i primitivan, a njegova glavna ideja je uzeti kandidate za blokiranje i unijeti pravila blokiranja u .htaccess datoteku između komentara
# start_auto_deny_list i # end_auto_deny_list

Sada se "štetne" IP adrese same blokiraju, 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 ploči hostinga:

Identificiramo potencijalne "zle" robote i blokiramo ih prema IP-u

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

Izvor: www.habr.com

Dodajte komentar