Identyfikujemy potencjalne „złe” boty i blokujemy je według adresu IP

Identyfikujemy potencjalne „złe” boty i blokujemy je według adresu IP

Dobry dzień! W artykule opowiem jak użytkownicy zwykłego hostingu mogą wyłapać adresy IP, które generują nadmierne obciążenie strony, a następnie zablokować je za pomocą narzędzi hostingowych, będzie „trochę” kodu php, kilka zrzutów ekranu.

Dane wejściowe:

  1. Strona internetowa stworzona na CMS WordPress
  2. Hosting Beget (to nie jest reklama, ale zrzuty ekranu panelu administracyjnego będą pochodzić od tego konkretnego dostawcy hostingu)
  3. Witryna WordPress została uruchomiona gdzieś na początku 2000 roku i zawiera dużą liczbę artykułów i materiałów
  4. Wersja PHP 7.2
  5. WP ma najnowszą wersję
  6. Od pewnego czasu strona zaczęła generować duże obciążenie MySQL zgodnie z danymi hostingu. Każdego dnia wartość ta przekraczała 120% normy na konto
  7. Według Yandexa. Serwis Metrica odwiedza dziennie 100-200 osób

Przede wszystkim zrobiono to:

  1. Tabele bazy danych zostały oczyszczone z nagromadzonych śmieci
  2. Wyłączono niepotrzebne wtyczki, usunięto sekcje nieaktualnego kodu

Jednocześnie chciałbym zwrócić uwagę na fakt, że opcje buforowania (wtyczki buforujące) były próbowane, poczyniono obserwacje - ale obciążenie 120% z jednej strony nie uległo zmianie i mogło tylko wzrosnąć.

Jak wyglądało przybliżone obciążenie baz danych hostingowych

Identyfikujemy potencjalne „złe” boty i blokujemy je według adresu IP
Na górze znajduje się dana witryna, tuż poniżej znajdują się inne witryny, które mają te same cms i mniej więcej taki sam ruch, ale powodują mniejsze obciążenie.

Analiza

  • Czyniono wiele prób z opcją buforowania danych, obserwacje prowadzono przez kilka tygodni (na szczęście przez ten czas hosting nigdy do mnie nie napisał, że jestem taki zły i zostanę rozłączony)
  • Nastąpiła analiza i wyszukiwanie wolnych zapytań, następnie nieznacznie zmieniono strukturę bazy danych i typ tabeli
  • Do analizy korzystaliśmy przede wszystkim z wbudowanego AWStats (swoją drogą pomogło to wyliczyć najgorszy adres IP na podstawie natężenia ruchu
  • Metryka - metryka dostarcza informacji tylko o ludziach, a nie o botach
  • Próbowano używać wtyczek do WP, które mogą filtrować i blokować odwiedzających nawet według kraju lokalizacji i różnych kombinacji
  • Całkowicie radykalnym sposobem okazało się zamknięcie witryny na jeden dzień z dopiskiem „Jesteśmy w konserwacji” - zrobiono to również za pomocą słynnej wtyczki. W tym przypadku spodziewamy się spadku obciążenia, ale nie do wartości zerowej, gdyż ideologia WP opiera się na hookach, a wtyczki rozpoczynają swoją aktywność w momencie wystąpienia „haczyka”, a przed wystąpieniem „haczyka” żądania do bazy danych mogą już być wykonane

Pomysł

  1. Oblicz adresy IP, które wysyłają wiele żądań w krótkim czasie.
  2. Zapisz liczbę wejść na stronę
  3. Blokuj dostęp do witryny na podstawie liczby odsłon
  4. Zablokuj przy użyciu wpisu „Odmów od” w pliku .htaccess
  5. Nie brałem pod uwagę innych opcji, takich jak iptables i reguły dla Nginx, ponieważ piszę o hostingu

Pojawił się pomysł, więc trzeba go wdrożyć, bo bez tego...

  • Tworzenie tabel do gromadzenia danych
    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;
    
  • Stwórzmy plik, w którym umieścimy kod. Kod zostanie zapisany w blokujących tabelach kandydatów i zachowa historię na potrzeby debugowania.

    Kod pliku do zapisywania adresów IP

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

    Istotą kodu jest pobranie adresu IP odwiedzającego i zapisanie go w tabeli. Jeżeli ip jest już w tabeli to pole cnt zostanie zwiększone (ilość żądań do serwisu)

  • Teraz coś strasznego... Teraz spalą mnie za moje czyny :)
    Aby nagrać każde żądanie do serwisu, łączymy kod pliku z głównym plikiem WordPressa - wp-load.php. Tak, zmieniamy plik jądra i właśnie po tym, jak zmienna globalna $wpdb już istnieje

Więc teraz możemy zobaczyć, jak często ten lub inny adres IP jest zaznaczony w naszej tabeli i z kubkiem kawy patrzymy tam raz na 5 minut, aby zrozumieć obraz

Identyfikujemy potencjalne „złe” boty i blokujemy je według adresu IP

Następnie po prostu skopiuj „szkodliwy” adres IP, otwórz plik .htaccess i dodaj go na końcu pliku

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

To tyle, teraz 94.242.55.248 - nie ma dostępu do serwisu i nie generuje obciążenia w bazie danych

Jednak za każdym razem takie ręczne kopiowanie nie jest zbyt słuszne, a poza tym kod miał być autonomiczny

Dodajmy plik, który będzie wykonywany przez CRON co 30 minut:

Kod pliku modyfikujący .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 pliku jest dość prosty i prymitywny, a jego główną ideą jest pobieranie kandydatów do blokowania i wprowadzanie reguł blokowania w pliku .htaccess pomiędzy komentarzami
# start_auto_deny_list i # end_auto_deny_list

Teraz „szkodliwe” adresy IP są same blokowane, a plik .htaccess wygląda mniej więcej tak:

# 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

W efekcie po tym jak ten kod zacznie działać, efekt można zobaczyć w panelu hostingowym:

Identyfikujemy potencjalne „złe” boty i blokujemy je według adresu IP

PS: Materiał jest autorstwa autora, choć jego część opublikowałem na swojej stronie, to wersję bardziej rozbudowaną dostałem na Habre.

Źródło: www.habr.com

Dodaj komentarz