Mes nustatome galimus „piktuosius“ robotus ir blokuojame juos pagal IP

Mes nustatome galimus „piktuosius“ robotus ir blokuojame juos pagal IP

Gera diena! Straipsnyje papasakosiu, kaip įprasto prieglobos vartotojai gali sugauti IP adresus, kurie generuoja per didelę svetainės apkrovą, o tada blokuoti juos naudodami prieglobos įrankius, bus „šiek tiek“ php kodo, keletas ekrano kopijų.

Įvesties duomenys:

  1. Svetainė sukurta naudojant TVS WordPress
  2. Hostingas „Beget“ (tai nėra reklama, bet administratoriaus skydelio ekrano kopijos bus iš šio konkretaus prieglobos paslaugų teikėjo)
  3. „WordPress“ svetainė buvo paleista kažkur 2000 m. pradžioje ir joje yra daug straipsnių ir medžiagos
  4. PHP versija 7.2
  5. WP turi naujausią versiją
  6. Jau kurį laiką svetainė pradėjo generuoti didelę MySQL apkrovą pagal prieglobos duomenis. Kiekvieną dieną ši vertė viršijo 120% sąskaitos normos
  7. Pagal „Yandex. Metricos svetainę per dieną aplanko 100-200 žmonių

Visų pirma tai buvo padaryta:

  1. Duomenų bazių lentelės buvo išvalytos nuo susikaupusių šiukšlių
  2. Nereikalingi įskiepiai buvo išjungti, pašalintos pasenusio kodo dalys

Tuo pačiu noriu atkreipti dėmesį į tai, kad buvo išbandytos talpyklos galimybės (caching pluginai), atlikti stebėjimai – tačiau 120% apkrova iš vienos svetainės nepakito ir galėjo tik augti.

Kaip atrodė apytikslis prieglobos duomenų bazių apkrovimas

Mes nustatome galimus „piktuosius“ robotus ir blokuojame juos pagal IP
Viršuje yra aptariama svetainė, šiek tiek žemiau yra kitos svetainės, kuriose yra tokie patys cms ir maždaug toks pat srautas, tačiau sukuriama mažiau apkrovos.

Analizė

  • Buvo daug bandymų su duomenų kaupimo talpyklomis parinktimis, stebėjimai buvo atliekami kelias savaites (laimei, per tą laiką priegloba man neparašė, kad man taip blogai ir būsiu atjungtas)
  • Buvo atlikta lėtų užklausų analizė ir paieška, vėliau buvo šiek tiek pakeista duomenų bazės struktūra ir lentelės tipas
  • Analizei pirmiausia naudojome įtaisytuosius AWStats (beje, tai padėjo apskaičiuoti blogiausią IP adresą pagal srautą
  • Metrika – metrika pateikia informaciją tik apie žmones, o ne apie robotus
  • Buvo bandoma WP naudoti papildinius, kurie gali filtruoti ir blokuoti lankytojus net pagal vietos šalį ir įvairius derinius
  • Pasirodė visiškai radikalus būdas uždaryti svetainę vienai dienai su užrašu „Mes atliekame techninę priežiūrą“ - tai taip pat buvo padaryta naudojant garsųjį papildinį. Šiuo atveju tikimės, kad apkrova sumažės, bet ne iki nulinių reikšmių, nes WP ideologija remiasi kabliukais, o įskiepiai pradeda savo veiklą, kai įvyksta „užkabinimas“, o prieš įvykstant „užkabinimui“, užklausos į duomenų bazę gali jau padaryta

Idėja

  1. Apskaičiuokite IP adresus, kurie per trumpą laiką pateikia daug užklausų.
  2. Užrašykite apsilankymų svetainėje skaičių
  3. Blokuokite prieigą prie svetainės pagal apsilankymų skaičių
  4. Blokuokite naudodami .htaccess failo įrašą „Deny from“.
  5. Aš nesvarsčiau kitų galimybių, pvz., „iptables“ ir „Nginx“ taisyklių, nes rašau apie prieglobą

Idėja atsirado, todėl ją reikia įgyvendinti, kaip be šito...

  • Lentelių kūrimas duomenims kaupti
    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;
    
  • Sukurkime failą, kuriame įdėsime kodą. Kodas įrašys į blokavimo kandidatų lenteles ir saugos derinimo istoriją.

    Failo kodas IP adresams įrašyti

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

    Kodo esmė – gauti lankytojo IP adresą ir įrašyti jį į lentelę. Jei ip jau yra lentelėje, cnt laukas bus padidintas (užklausų į svetainę skaičius)

  • Dabar baisus dalykas... Dabar jie mane sudegins už mano veiksmus :)
    Norėdami įrašyti kiekvieną užklausą svetainėje, failo kodą prijungiame prie pagrindinio WordPress failo - wp-load.php. Taip, mes keičiame branduolio failą ir būtent po to, kai visuotinis kintamasis $wpdb jau egzistuoja

Taigi, dabar matome, kaip dažnai mūsų lentelėje yra pažymėtas tas ar kitas IP adresas ir su kavos puodeliu žiūrime ten kartą per 5 minutes, kad suprastume vaizdą

Mes nustatome galimus „piktuosius“ robotus ir blokuojame juos pagal IP

Tada tiesiog nukopijuokite „žalingą“ IP, atidarykite .htaccess failą ir pridėkite jį prie failo pabaigos

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

Tai viskas, dabar 94.242.55.248 - neturi prieigos prie svetainės ir negeneruoja duomenų bazės apkrovos

Tačiau kiekvieną kartą taip kopijuoti ranka nėra labai teisinga užduotis, be to, kodas turėjo būti savarankiškas

Pridėkime failą, kuris bus vykdomas per CRON kas 30 minučių:

Failo kodas modifikuojantis .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)));

Failo kodas yra gana paprastas ir primityvus, o pagrindinė jo idėja yra pasirinkti blokuojamus kandidatus ir įvesti blokavimo taisykles .htaccess faile tarp komentarų
# start_auto_deny_list ir # end_auto_deny_list

Dabar „kenksmingi“ IP blokuojami patys, o .htaccess failas atrodo maždaug taip:

# 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

Dėl to, kai šis kodas pradės veikti, prieglobos skydelyje galite pamatyti rezultatą:

Mes nustatome galimus „piktuosius“ robotus ir blokuojame juos pagal IP

PS: Medžiaga yra autoriaus, nors dalį jos paskelbiau savo svetainėje, gavau išsamesnę Habre versiją.

Šaltinis: www.habr.com

Добавить комментарий