Við greinum hugsanlega „vonda“ vélmenni og lokum á þá með IP

Við greinum hugsanlega „vonda“ vélmenni og lokum á þá með IP

Góðan dag! Í greininni mun ég segja þér hvernig notendur venjulegrar hýsingar geta náð IP tölum sem mynda of mikið álag á síðuna og síðan lokað þeim með því að nota hýsingartæki, það verður „smá“ af php kóða, nokkrar skjámyndir.

Inntaksgögn:

  1. Vefsíða búin til á CMS WordPress
  2. Hýsing Beget (þetta er ekki auglýsing, en skjámyndir stjórnborðsins verða frá þessari tilteknu hýsingaraðila)
  3. WordPress síða var opnuð einhvers staðar snemma árs 2000 og hefur mikinn fjölda greina og efnis
  4. PHP útgáfa 7.2
  5. WP er með nýjustu útgáfuna
  6. Í nokkurn tíma byrjaði síðan að búa til mikið álag á MySQL samkvæmt hýsingargögnunum. Á hverjum degi fór þetta gildi yfir 120% af norminu á reikning
  7. Samkvæmt Yandex. Metrica síða er heimsótt af 100-200 manns á dag

Í fyrsta lagi var þetta gert:

  1. Gagnagrunnstöflur voru hreinsaðar af uppsöfnuðu sorpi
  2. Óþarfa viðbætur voru óvirkar, hlutar úr úreltum kóða voru fjarlægðir

Jafnframt vil ég vekja athygli á því að valmöguleikar í skyndiminni (caching plugins) voru prófaðir, athuganir voru gerðar - en 120% álag frá einni síðu var óbreytt og gat aðeins vaxið.

Hvernig áætlað álag á hýsingargagnagrunna leit út

Við greinum hugsanlega „vonda“ vélmenni og lokum á þá með IP
Efst er vefsíðan sem um ræðir, rétt fyrir neðan eru aðrar síður sem hafa sömu cms og um það bil sömu umferð, en skapa minna álag.

Greining

  • Margar tilraunir voru gerðar með gagnageymslumöguleikum, athuganir voru gerðar á nokkrum vikum (sem betur fer skrifaði hýsingin mér aldrei á þessum tíma að ég væri svona slæm og yrði aftengd)
  • Það var greining og leit að hægum fyrirspurnum, síðan var gagnagrunnsskipan og töflugerð breytt lítillega
  • Við greiningu notuðum við fyrst og fremst innbyggðu AWStats (við the vegur, það hjálpaði til við að reikna út verstu IP tölu byggt á umferðarmagni
  • Mæling - mæligildið veitir aðeins upplýsingar um fólk, ekki um vélmenni
  • Það hafa verið tilraunir til að nota viðbætur fyrir WP sem geta síað og lokað fyrir gesti jafnvel eftir staðsetningarlandi og ýmsum samsetningum
  • Algjörlega róttæk leið reyndist vera að loka síðunni í einn dag með athugasemdinni „Við erum í viðhaldi“ - þetta var líka gert með því að nota hið fræga viðbót. Í þessu tilviki gerum við ráð fyrir að álagið lækki, en ekki að núllgildi, þar sem WP hugmyndafræðin byggist á krókum og viðbætur hefja virkni sína þegar „krók“ á sér stað, og áður en „krók“ á sér stað geta beiðnir í gagnagrunninn þegar verið gerðar

Hugmynd

  1. Reiknaðu IP tölur sem gera mikið af beiðnum á stuttum tíma.
  2. Skráðu fjölda heimsókna á síðuna
  3. Lokaðu aðgangi að síðunni miðað við fjölda heimsókna
  4. Lokaðu með því að nota „Neita frá“ færslunni í .htaccess skránni
  5. Ég íhugaði ekki aðra valkosti, eins og iptables og reglur fyrir Nginx, vegna þess að ég er að skrifa um hýsingu

Hugmynd hefur komið fram og því þarf að útfæra hana þar sem án þessa...

  • Að búa til töflur til að safna gögnum
    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;
    
  • Við skulum búa til skrá þar sem við setjum kóðann. Kóðinn mun skrá í töflunum sem hindrar umsækjendur og geymir feril fyrir villuleit.

    Skráarkóði til að skrá IP tölur

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

    Kjarninn í kóðanum er að fá IP tölu gestsins og skrifa hana inn í töflu. Ef ip er nú þegar í töflunni mun cnt reiturinn aukast (fjöldi beiðna á síðuna)

  • Nú er það ógnvekjandi... Nú munu þeir brenna mig fyrir gjörðir mínar :)
    Til að skrá hverja beiðni á síðuna tengjum við skráarkóðann við aðal WordPress skrána - wp-load.php. Já, við breytum kjarnaskránni og nákvæmlega eftir að alþjóðlega breytan $wpdb er þegar til

Svo, nú getum við séð hversu oft þetta eða hitt IP-talan er merkt í töflunni okkar og með kaffibolla lítum við þangað einu sinni á 5 mínútna fresti til að skilja myndina

Við greinum hugsanlega „vonda“ vélmenni og lokum á þá með IP

Afritaðu síðan einfaldlega „skaðlega“ IP, opnaðu .htaccess skrána og bættu henni við endann á skránni

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

Það er það, núna 94.242.55.248 - hefur ekki aðgang að síðunni og myndar ekki álag á gagnagrunninn

En í hvert skipti að afrita með höndunum eins og þetta er ekki mjög réttlátt verkefni, og að auki var kóðanum ætlað að vera sjálfstætt

Við skulum bæta við skrá sem verður keyrð í gegnum CRON á 30 mínútna fresti:

Skráarkóði breytir .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)));

Skráarkóðinn er frekar einfaldur og frumstæður og meginhugmynd hans er að taka frambjóðendur til að loka og slá inn blokkunarreglur í .htaccess skrána á milli athugasemda
# start_auto_deny_list og # end_auto_deny_list

Nú er „skaðlegum“ IP-tölum læst af sjálfu sér og .htaccess skráin lítur svona út:

# 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

Þar af leiðandi, eftir að þessi kóði byrjar að virka, geturðu séð niðurstöðuna á hýsingarborðinu:

Við greinum hugsanlega „vonda“ vélmenni og lokum á þá með IP

PS: Efnið er höfundar, þó að ég hafi birt hluta þess á vefsíðunni minni, fékk ég stækkaðri útgáfu á Habre.

Heimild: www.habr.com

Bæta við athugasemd