God dag! I artikkelen vil jeg fortelle deg hvordan brukere av vanlig hosting kan fange IP-adresser som genererer overdreven belastning på nettstedet og deretter blokkere dem ved hjelp av hostingverktøy, det vil være "litt" php-kode, noen få skjermbilder.
Inndata:
- Nettsted laget på CMS WordPress
- Hosting Beget (dette er ikke en reklame, men skjermbildene fra adminpanelet vil være fra denne spesifikke vertsleverandøren)
- WordPress-siden ble lansert et sted tidlig i 2000 og har et stort antall artikler og materialer
- PHP versjon 7.2
- WP har den nyeste versjonen
- I en tid nå begynte siden å generere en høy belastning på MySQL i henhold til vertsdataene. Hver dag oversteg denne verdien 120 % av normen per konto
- I følge Yandex. Metrica-nettstedet besøkes av 100-200 personer per dag
Først og fremst ble dette gjort:
- Databasetabeller ble ryddet for akkumulert søppel
- Unødvendige plugins ble deaktivert, deler av utdatert kode ble fjernet
Samtidig vil jeg gjøre deg oppmerksom på det faktum at caching-alternativer (caching-plugins) ble prøvd, observasjoner ble gjort - men belastningen på 120% fra ett nettsted var uendret og kunne bare vokse.
Hvordan den omtrentlige belastningen på vertsdatabaser så ut
Øverst er det aktuelle nettstedet, rett under er andre nettsteder som har samme cms og omtrent samme trafikk, men som skaper mindre belastning.
Analyse
- Det ble gjort mange forsøk med alternativer for databufring, observasjoner ble utført over flere uker (heldigvis, i løpet av denne tiden skrev vertskapet aldri til meg at jeg var så dårlig og ville bli frakoblet)
- Det ble en analyse og søk etter trege søk, deretter ble databasestrukturen og tabelltypen endret litt
- For analyse brukte vi først og fremst de innebygde AWStats (det hjalp forresten å beregne den dårligste IP-adressen basert på trafikkvolum
- Metrikk – beregningen gir kun informasjon om personer, ikke om roboter
- Det har vært forsøk på å bruke plugins for WP som kan filtrere og blokkere besøkende selv etter land og forskjellige kombinasjoner
- En helt radikal måte viste seg å være å stenge nettstedet for en dag med meldingen "Vi er under vedlikehold" - dette ble også gjort ved hjelp av den berømte plugin. I dette tilfellet forventer vi at belastningen faller, men ikke til null verdier, siden WP-ideologien er basert på kroker og plugins begynner sin aktivitet når en "hook" oppstår, og før "hook" oppstår, kan forespørsler til databasen allerede være laget
Idé
- Beregn IP-adresser som gjør mange forespørsler på kort tid.
- Registrer antall treff på nettstedet
- Blokker tilgang til nettstedet basert på antall treff
- Blokkér ved å bruke «Nekt fra»-oppføringen i .htaccess-filen
- Jeg vurderte ikke andre alternativer, som iptables og regler for Nginx, fordi jeg skriver om hosting
En idé har dukket opp, så den må implementeres, siden uten denne...
- Lage tabeller for å samle 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;
- La oss lage en fil der vi skal plassere koden. Koden vil registrere i de blokkerende kandidattabellene og holde en historikk for feilsøking.
Filkode for registrering 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);
Essensen av koden er å få den besøkendes IP-adresse og skrive den inn i en tabell. Hvis ip-en allerede er i tabellen, vil cnt-feltet økes (antall forespørsler til nettstedet)
- Nå er det skumle... Nå skal de brenne meg for mine handlinger :)
For å registrere hver forespørsel til nettstedet, kobler vi filkoden til hoved WordPress-filen - wp-load.php. Ja, vi endrer kjernefilen og nøyaktig etter at den globale variabelen $wpdb allerede eksisterer
Så nå kan vi se hvor ofte denne eller den IP-adressen er merket i tabellen vår, og med et kopp kaffe ser vi dit en gang hvert 5. minutt for å forstå bildet
Deretter kopierer du bare den "skadelige" IP-en, åpner .htaccess-filen og legger den til på slutten av filen
Order allow,deny
Allow from all
# start_auto_deny_list
Deny from 94.242.55.248
# end_auto_deny_list
Det er det, nå 94.242.55.248 - har ikke tilgang til nettstedet og genererer ikke belastning på databasen
Men hver gang å kopiere for hånd som dette er ikke en veldig rettferdig oppgave, og dessuten var koden ment å være autonom
La oss legge til en fil som vil bli utført via CRON hvert 30. minutt:
Filkode som endrer .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 er ganske enkel og primitiv, og hovedideen er å ta kandidater for blokkering og angi blokkeringsregler i .htaccess-filen mellom kommentarene
# start_auto_deny_list og # end_auto_deny_list
Nå er "skadelige" IP-er blokkert av seg selv, og .htaccess-filen ser omtrent slik ut:
# 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 et resultat, etter at denne koden begynner å fungere, kan du se resultatet i vertspanelet:
PS: Materialet er forfatterens, selv om jeg publiserte deler av det på nettsiden min, fikk jeg en mer utvidet versjon på Habre.
Kilde: www.habr.com