
Goedemiddag! In dit artikel vertel ik hoe gebruikers van gewone hosting IP-adressen kunnen opsporen die overmatige belasting op de site veroorzaken, en deze vervolgens kunnen blokkeren met de tools van de hosting, er zal een 'beetje' php-code en enkele screenshots worden gedeeld.
Invoergegevens:
- Website gebouwd op CMS WordPress
- Hosting Beget (dit is geen reclame, maar de screenshots van het dashboard zullen precies van deze hostingprovider zijn)
- De WordPress-site is ergens begin 2000 gelanceerd en heeft een groot aantal artikelen en materialen.
- PHP-versie 7.2
- WP heeft de nieuwste versie.
- Sinds enige tijd begint de site hoge belasting op MySQL te genereren, volgens de hosting. Elke dag overschreed deze waarde 120% van de norm voor het account.
- Volgens Yandex.Metrica heeft de site 100-200 bezoekers per dag.
De eerste stappen waren:
- De databanktabellen zijn schoongemaakt van opgehoopte rommel.
- Onnodige plugins zijn uitgeschakeld en verouderde code is verwijderd.
Ik wil opmerken dat verschillende cachingopties (caching-plugins) zijn getest, en monitoring is uitgevoerd — maar de belasting van 120% van één site bleef onveranderd en kon alleen maar toenemen.
Zo zag de geschatte belasting op de databases van de hosting eruit.

Bovenaan staat de site waar het over gaat, iets lager andere sites met hetzelfde CMS en vergelijkbare bezoeken, maar deze veroorzaken minder belasting.
Analyse
- Er waren veel pogingen met verschillende cachingopties, en observaties zijn gedurende enkele weken gedaan (gelukkig heeft de hosting in deze tijd nooit gezegd dat ik zo slecht ben en dat ze me zouden uitschakelen).
- Er was een analyse en zoektocht naar langzame verzoeken, daarna is de database-structuur en het type tabellen enigszins aangepast.
- Voor de analyse werd in de eerste plaats gebruikgemaakt van de ingebouwde AWStats (hiervoor heeft het me eigenlijk geholpen om het meest kwaadaardige IP-adres op basis van dataverkeer te berekenen).
- Statistieken — de statistieken geven alleen informatie over mensen, en niet over bots.
- Er zijn pogingen gedaan om plugins voor WP te gebruiken, die bezoekers zelfs op basis van land van herkomst kunnen filteren en blokkeren met verschillende combinaties.
- Een absoluut radicale manier om de website een dag te sluiten met de vermelding 'We zijn bezig met onderhoud' - dit werd ook gedaan met behulp van de beroemde plugin. In dit geval zou de belasting naar verwachting zijn gedaald, maar niet naar nul, aangezien de ideologie van WP gebaseerd is op hooks en plugins hun activiteit beginnen bij het optreden van een bepaalde 'hook', en vóór het optreden van de 'hook' al verzoeken aan de database zijn gedaan.
Idee
- Bereken de IP-adressen die veel verzoeken doen in een kort tijdsbestek.
- Bepaal het aantal bezoeken aan de website.
- Blokkeer de toegang tot de website op basis van het aantal bezoeken.
- Blokkeer met behulp van de opdracht 'Deny from' in het .htaccess-bestand.
- Andere opties, zoals iptables en regels voor Nginx, worden niet overwogen, omdat ik over hosting schrijf.
Als het idee is ontstaan, moet het worden gerealiseerd, hoe kan het ook anders...
- We creëren tabellen voor het verzamelen van gegevens.
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='Kandidaten voor blokkering' 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='Lijst van reeds geblokkeerde' 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='Geschiedenis van alle verzoeken voor debugging' COLLATE='utf8_general_ci' ENGINE=InnoDB AUTO_INCREMENT=1; - Laten we een bestand maken waarin we de code plaatsen. De code zal records in de blokkeringstabel invoeren en een geschiedenis voor debugging bijhouden.
Bestandscode voor het opslaan van IP-adressen.
<?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);De essentie van de code is om het IP-adres van de bezoeker te verkrijgen en het in de tabel op te slaan. Als het IP al in de tabel staat, zal het veld cnt (aantal verzoeken naar de site) worden verhoogd.
- Nu het enge... Straks word ik verbrand voor mijn acties 🙂
Om elk bezoek aan de website vast te leggen, verbinden we de bestandscode in het hoofd WordPress-bestand - wp-load.php. Ja, we wijzigen het kernelbestand en juist nadat de globale variabele $wpdb al bestaat.
Dus, nu kunnen we zien hoe vaak elk IP-adres in onze tabel voorkomt, en met een kopje koffie bekijken we dit om de vijf minuten om een beter beeld te krijgen.

Vervolgens is het eenvoudig: we hebben het 'schadelijke' IP gekopieerd, het .htaccess-bestand geopend en aan het einde van het bestand toegevoegd.
Order allow,deny
Allow from all
# start_auto_deny_list
Deny from 94.242.55.248
# end_auto_deny_list
Dat is het, nu heeft 94.242.55.248 geen toegang tot de site en genereert het geen belasting op de database.
Maar elke keer met de hand kopiëren is niet echt praktisch, en bovendien was de code bedoeld als autonoom.
Laten we een bestand toevoegen dat elke 30 minuten door CRON wordt uitgevoerd:
Code van het bestand dat .htaccess aanpast
get_results("SELECT * FROM wp_visiters_bot WHERE cnt>{$limit_cnt}");
$new_blocked = [];
$exclude_ip = [
'87.236.16.70' // adres van de hosting
];
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;
}
// Opruimen van de tabel
$wpdb->query("DELETE FROM wp_visiters_bot");
//echo '';print_r($new_blocked);echo '';
$file = '.htaccess';
$start_searche_tag = 'start_auto_deny_list';
$end_searche_tag = 'end_auto_deny_list';
$handle = @fopen($file, "r");
if ($handle) {
$replace_string = ''; // Test voor invoegen in .htaccess
$target_content = false; // Vlag voor het juiste gedeelte van de code
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 "Fout: fgets() faalde onverwacht";
}
fclose($handle);
}
// Huidig .htaccess-bestand
$content = file_get_contents($file);
$content = str_replace($replace_string, '', $content);
// Verwijder alle blokkades in .htaccess
file_put_contents($file, $content);
// Nieuwe blokkades schrijven
$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)));
De code van het bestand is vrij eenvoudig en primitief, en het belangrijkste idee is om kandidaten voor blokkade te nemen en blokkade regels in het .htaccess-bestand tussen de commentaren in te voegen.
# start_auto_deny_list и # end_auto_deny_list
Nu worden 'schadelijke' IP's automatisch geblokkeerd en ziet het .htaccess-bestand er ongeveer zo uit:
# 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
Uiteindelijk, na het activeren van deze code, kun je het resultaat zien in het hostingpaneel:

PS: Dit materiaal is origineel, hoewel ik een deel ervan op mijn website heb gepubliceerd, is er op Habre een uitgebreidere versie verschenen.
Bron: habr.com
