Ni identigas eblajn "malbonajn" robotojn kaj blokas ilin per IP

Ni identigas eblajn "malbonajn" robotojn kaj blokas ilin per IP

Bonan tagon! En la artikolo mi rakontos al vi kiel uzantoj de regula gastigado povas kapti IP-adresojn, kiuj generas troan ŝarĝon en la retejo kaj poste bloki ilin per gastigaj iloj, estos "iom" da php-kodo, kelkaj ekrankopioj.

Eniga datumoj:

  1. Retejo kreita sur CMS WordPress
  2. Gastiganta Beget (ĉi tio ne estas reklamo, sed la ekrankopioj de la administra panelo estos de ĉi tiu speciala gastiga provizanto)
  3. La WordPress-ejo estis lanĉita ie komence de 2000 kaj havas grandan nombron da artikoloj kaj materialoj
  4. PHP-versio 7.2
  5. WP havas la lastan version
  6. De iom da tempo, la retejo komencis generi altan ŝarĝon sur MySQL laŭ la gastigaj datumoj. Ĉiutage ĉi tiu valoro superis 120% de la normo por konto
  7. Laŭ Yandex. Metrica-ejo estas vizitata de 100-200 homoj ĉiutage

Antaŭ ĉio, ĉi tio estis farita:

  1. Datumbaztabloj estis purigitaj de amasigita rubo
  2. Nenecesaj aldonaĵoj estis malŝaltitaj, sekcioj de malmoderna kodo estis forigitaj

Samtempe mi ŝatus atentigi vin pri tio, ke oni provis kaŝajn opciojn (kaŝajn kromaĵojn), oni faris observojn - sed la ŝarĝo de 120% de unu retejo estis senŝanĝa kaj povis nur kreski.

Kia aspektis la proksimuma ŝarĝo pri gastigaj datumbazoj

Ni identigas eblajn "malbonajn" robotojn kaj blokas ilin per IP
Supre estas la koncerna retejo, ĝuste sube estas aliaj retejoj, kiuj havas la samajn cms kaj proksimume la saman trafikon, sed kreas malpli da ŝarĝo.

Анализ

  • Multaj provoj estis faritaj kun datumoj-kaŝkaptado-opcioj, observoj estis faritaj dum pluraj semajnoj (feliĉe, dum ĉi tiu tempo la gastigado neniam skribis al mi, ke mi estas tiel malbona kaj estus malkonektita)
  • Estis analizo kaj serĉado de malrapidaj demandoj, tiam la datumbaza strukturo kaj tabeltipo estis iomete ŝanĝitaj
  • Por analizo, ni ĉefe uzis la enkonstruitan AWStats (cetere, ĝi helpis kalkuli la plej malbonan IP-adreson surbaze de trafika volumo
  • Metriko - la metriko provizas informojn nur pri homoj, ne pri robotoj
  • Estis provoj uzi kromaĵojn por WP, kiuj povas filtri kaj bloki vizitantojn eĉ laŭ lando de loko kaj diversaj kombinaĵoj.
  • Tute radikala maniero montriĝis fermi la retejon dum tago per la noto "Ni estas sub prizorgado" - ĉi tio ankaŭ estis farita per la fama kromaĵo. En ĉi tiu kazo, ni atendas ke la ŝarĝo falos, sed ne al nul valoroj, ĉar la WP-ideologio baziĝas sur hokoj kaj kromprogramoj komencas sian agadon kiam "hoko" okazas, kaj antaŭ ol la "hoko" okazas, petoj al la datumbazo povas. jam esti farita

Ideo

  1. Kalkulu IP-adresojn, kiuj faras multajn petojn en mallonga tempodaŭro.
  2. Notu la nombron da sukcesoj al la retejo
  3. Bloki aliron al la retejo laŭ la nombro da sukcesoj
  4. Bloku uzante la eniron "Deni de" en la dosiero .htaccess
  5. Mi ne konsideris aliajn eblojn, kiel iptables kaj reguloj por Nginx, ĉar mi skribas pri gastigado

Aperis ideo, do necesas efektivigi ĝin, ĉar sen ĉi tio...

  • Krei tabelojn por amasigi datumojn
    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;
    
  • Ni kreu dosieron en kiu ni metos la kodon. La kodo registros en la blokantaj kandidatoj-tabeloj kaj konservos historion por elpurigado.

    Dosiera kodo por registri IP-adresojn

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

    La esenco de la kodo estas akiri la IP-adreson de la vizitanto kaj skribi ĝin en tabelon. Se la ip jam estas en la tabelo, la cnt-kampo estos pliigita (la nombro da petoj al la retejo)

  • Nun la timiga afero... Nun ili bruligos min pro miaj agoj :)
    Por registri ĉiun peton al la retejo, ni konektas la dosierkodon al la ĉefa WordPress-dosiero - wp-load.php. Jes, ni ŝanĝas la kernan dosieron kaj ĝuste post la tutmonda variablo $wpdb jam ekzistas

Do, nun ni povas vidi kiom ofte tiu aŭ alia IP-adreso estas markita en nia tabelo kaj kun kafo kafo ni rigardas tien unufoje ĉiujn 5 minutojn por kompreni la bildon.

Ni identigas eblajn "malbonajn" robotojn kaj blokas ilin per IP

Tiam simple kopiu la "malutilan" IP, malfermu la .htaccess-dosieron kaj aldonu ĝin al la fino de la dosiero.

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

Jen ĝi, nun 94.242.55.248 - ne havas aliron al la retejo kaj ne generas ŝarĝon en la datumbazo

Sed ĉiufoje kopii mane tiel ne estas tre justa tasko, kaj krome, la kodo estis celita esti aŭtonoma

Ni aldonu dosieron, kiu estos ekzekutita per CRON ĉiujn 30 minutojn:

Dosiera kodo modifante .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)));

La dosierkodo estas sufiĉe simpla kaj primitiva kaj ĝia ĉefa ideo estas preni kandidatojn por blokado kaj enigi regulojn pri blokado en la dosiero .htaccess inter la komentoj.
# start_auto_deny_list kaj # end_auto_deny_list

Nun "malutilaj" IP-oj estas blokitaj per si mem, kaj la .htaccess-dosiero aspektas kiel ĉi tio:

# 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

Kiel rezulto, post kiam ĉi tiu kodo ekfunkcias, vi povas vidi la rezulton en la gastiga panelo:

Ni identigas eblajn "malbonajn" robotojn kaj blokas ilin per IP

PS: La materialo estas de la aŭtoro, kvankam mi publikigis parton de ĝi en mia retejo, mi ricevis pli vastigitan version ĉe Habre.

fonto: www.habr.com

Aldoni komenton