Vi identifierar potentiella "onda" bots och blockerar dem via IP

Vi identifierar potentiella "onda" bots och blockerar dem via IP

God dag! I artikeln kommer jag att berätta hur användare av vanlig värd kan fånga IP-adresser som genererar överdriven belastning på webbplatsen och sedan blockera dem med hjälp av värdverktyg, det kommer att finnas "lite" php-kod, några skärmdumpar.

Indata:

  1. Webbplats skapad på CMS WordPress
  2. Hosting Beget (detta är inte en reklam, men adminpanelens skärmdumpar kommer från just denna värdleverantör)
  3. WordPress-sajten lanserades någonstans i början av 2000 och har ett stort antal artiklar och material
  4. PHP version 7.2
  5. WP har den senaste versionen
  6. Sedan en tid tillbaka har sajten börjat generera en hög belastning på MySQL enligt värddata. Varje dag översteg detta värde 120 % av normen per konto
  7. Enligt Yandex. Metricas webbplats besöks av 100-200 personer per dag

Först och främst gjordes detta:

  1. Databastabeller rensades från ackumulerat sopor
  2. Onödiga plugins inaktiverades, delar av föråldrad kod togs bort

Samtidigt vill jag uppmärksamma er på det faktum att cachningsalternativ (caching-plugins) testades, observationer gjordes - men belastningen på 120% från en sida var oförändrad och kunde bara växa.

Hur den ungefärliga belastningen på värddatabaser såg ut

Vi identifierar potentiella "onda" bots och blockerar dem via IP
Överst finns den aktuella sajten, precis nedanför finns andra sajter som har samma cms och ungefär samma trafik, men som skapar mindre belastning.

Analys

  • Många försök gjordes med datacachningsalternativ, observationer utfördes under flera veckor (lyckligtvis skrev hostingen under denna tid aldrig till mig att jag var så dålig och skulle kopplas bort)
  • Det gjordes en analys och sökning efter långsamma frågor, sedan ändrades databasstrukturen och tabelltypen något
  • För analys använde vi i första hand de inbyggda AWStats (förresten, det hjälpte till att beräkna den sämsta IP-adressen baserat på trafikvolym
  • Mätvärde – måtten ger endast information om personer, inte om botar
  • Det har gjorts försök att använda plugins för WP som kan filtrera och blockera besökare även efter platsland och olika kombinationer
  • Ett helt radikalt sätt visade sig vara att stänga webbplatsen för en dag med anteckningen "Vi är under underhåll" - detta gjordes också med den berömda plugin. I det här fallet förväntar vi oss att belastningen sjunker, men inte till nollvärden, eftersom WP-ideologin är baserad på krokar och plugins börjar sin aktivitet när en "hook" inträffar, och innan "hook" inträffar, kan förfrågningar till databasen redan gjorts

Idé

  1. Beräkna IP-adresser som gör många förfrågningar på kort tid.
  2. Registrera antalet träffar på webbplatsen
  3. Blockera åtkomst till webbplatsen baserat på antalet träffar
  4. Blockera med "Neka från"-posten i .htaccess-filen
  5. Jag övervägde inte andra alternativ, som iptables och regler för Nginx, eftersom jag skriver om värd

En idé har dykt upp, så den måste implementeras, eftersom utan denna...

  • Skapa tabeller för att samla data
    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;
    
  • Låt oss skapa en fil där vi ska placera koden. Koden kommer att spela in i de blockerande kandidattabellerna och behålla en historik för felsökning.

    Filkod för inspelning av IP-adresser

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

    Kärnan i koden är att få besökarens IP-adress och skriva in den i en tabell. Om ip:n redan finns i tabellen kommer cnt-fältet att ökas (antalet förfrågningar till webbplatsen)

  • Nu det läskiga... Nu ska de bränna mig för mina handlingar :)
    För att spela in varje begäran till webbplatsen kopplar vi filkoden till WordPress-huvudfilen - wp-load.php. Ja, vi ändrar kärnfilen och precis efter att den globala variabeln $wpdb redan existerar

Så nu kan vi se hur ofta den eller den IP-adressen är markerad i vår tabell och med en kopp kaffe tittar vi dit en gång var 5:e minut för att förstå bilden

Vi identifierar potentiella "onda" bots och blockerar dem via IP

Kopiera sedan helt enkelt den "skadliga" IP-adressen, öppna .htaccess-filen och lägg till den i slutet av filen

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

Det var allt, nu 94.242.55.248 - har inte tillgång till webbplatsen och genererar inte belastning på databasen

Men varje gång att kopiera för hand så här är inte en särskilt rättfärdig uppgift, och dessutom var koden avsedd att vara autonom

Låt oss lägga till en fil som kommer att köras via CRON var 30:e minut:

Filkod som ändrar .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)));

Filkoden är ganska enkel och primitiv och dess huvudidé är att ta kandidater för blockering och ange blockeringsregler i .htaccess-filen mellan kommentarerna
# start_auto_deny_list och # end_auto_deny_list

Nu blockeras "skadliga" IP-adresser av sig själva, och .htaccess-filen ser ut ungefär så här:

# 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

Som ett resultat, efter att den här koden börjar fungera, kan du se resultatet i värdpanelen:

Vi identifierar potentiella "onda" bots och blockerar dem via IP

PS: Materialet är författarens, även om jag publicerade en del av det på min hemsida, fick jag en mer utökad version på Habre.

Källa: will.com

Lägg en kommentar