
Buongiorno! In questo articolo parlerò di come gli utenti di hosting comune possano catturare gli indirizzi IP che generano un carico eccessivo sul sito e poi bloccarli utilizzando gli strumenti di hosting, ci sarà un po' di codice php e alcune schermate.
Dati di input:
- Sito creato su CMS WordPress
- Hosting Beget (non è pubblicità, ma gli screenshot dell'admin panel saranno di questo fornitore di hosting)
- Il sito WordPress è stato lanciato all'inizio degli anni 2000 e ha un grande numero di articoli e materiali.
- Versione PHP 7.2
- WP ha l'ultima versione.
- Da un po' di tempo, il sito ha iniziato a generare un alto carico su MySQL secondo i dati di hosting. Ogni giorno questo valore superava il 120% della norma per l'account.
- Secondo Yandex.Metrica, il sito viene visitato da 100-200 persone al giorno.
In primo luogo è stato fatto:
- Pulizia delle tabelle del database dai rifiuti accumulati.
- Disattivati i plugin non necessari, rimossi frammenti di codice obsoleto.
Inoltre, sottolineo che sono stati provati vari metodi di caching (plugin di caching), sono state effettuate osservazioni, ma il carico del 120% da un unico sito è rimasto invariato e potrebbe solo aumentare.
Questo è come appariva il carico approssimativo sui database di hosting.

In cima si trova il sito di cui si parla, subito sotto ci sono altri siti che hanno lo stesso cms e una visita simile, ma generano meno carico.
Analisi
- Sono stati fatti molti tentativi con vari metodi di caching dei dati, sono state fatte osservazioni per diverse settimane (fortunatamente, durante questo tempo, l'hosting non mi ha mai detto che ero così cattivo e che sarei stato disconnesso).
- È stata fatta un'analisi e una ricerca di query lente, poi è stata leggermente modificata la struttura del database e il tipo di tabelle.
- Per l'analisi è stato utilizzato principalmente AWStats integrato (che, tra l'altro, ha aiutato a calcolare il più 'cattivo' indirizzo IP in base al volume di traffico.
- La metrica - la metrica fornisce informazioni solo sulle persone, non sui bot.
- Ci sono stati tentativi di utilizzare plugin per WP che possono filtrare e bloccare i visitatori anche in base al paese di origine e a varie combinazioni.
- Un modo davvero radicale per chiudere un sito per un giorno con la dicitura 'Siamo in manutenzione' è stato fatto tramite il famoso plugin. In questo caso, ci si aspettava una riduzione del carico, ma non a zero, poiché l'ideologia di WP si basa su hook e i plugin iniziano la loro attività quando si verifica un 'hook', e prima che si verifichi un 'hook' potrebbero già essere stati effettuati richieste al DB.
Idea
- Calcolare gli indirizzi IP che effettuano molte richieste in un breve lasso di tempo.
- Registrare il numero di accessi al sito.
- Basandosi sul numero di accessi, bloccare l'accesso al sito.
- Bloccare utilizzando la scrittura 'Deny from' nel file .htaccess.
- Altre varianti, come iptables e regole per Nginx non vengono considerate, poiché scrivo di hosting.
E' emersa un'idea, quindi è necessario realizzarla, come potrebbe essere altrimenti...
- Creiamo tabelle per l'accumulo dei dati.
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='Candidati per il blocco' 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='Elenco già bloccati' 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='Cronologia di tutte le richieste per il debug' COLLATE='utf8_general_ci' ENGINE=InnoDB AUTO_INCREMENT=1; - Creeremo un file in cui inseriremo il codice. Il codice registrerà nei tavoli i candidati per il blocco e gestirà la cronologia per il debug.
Codice file per la registrazione degli indirizzi 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);La sostanza del codice è quella di ottenere l'indirizzo IP del visitatore e registrarlo nella tabella. Se l'IP è già presente nella tabella, verrà incrementato il campo cnt (numero di richieste al sito).
- Ora la cosa spaventosa... Adesso mi bruceranno per le mie azioni 🙂
Per registrare ogni accesso al sito, colleghiamo il codice del file nel file principale di WordPress - wp-load.php. Sì, stiamo modificando un file di sistema e precisamente dopo che la variabile globale $wpdb esiste già.
Ora possiamo vedere quanto frequentemente un determinato indirizzo IP viene segnalato nella nostra tabella e di tanto in tanto diamo un'occhiata con una tazza di caffè ogni 5 minuti per avere un'idea della situazione.

Poi è semplice, abbiamo copiato l'IP "maligno", aperto il file .htaccess e aggiunto alla fine del file.
Order allow,deny
Allow from all
# start_auto_deny_list
Deny from 94.242.55.248
# end_auto_deny_list
Tutto qui, ora 94.242.55.248 non ha accesso al sito e non genera carico sul database.
Ma copiare manualmente ogni volta non è un'occupazione molto giusta, e inoltre il codice era pensato per essere autonomo.
Aggiungiamo un file che verrà eseguito tramite CRON ogni 30 minuti:
Codice del file che modifica .htaccess
get_results("SELECT * FROM wp_visiters_bot WHERE cnt>{$limit_cnt}");
$new_blocked = [];
$exclude_ip = [
'87.236.16.70' // indirizzo dell'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;
}
// Pulizia della tabella
$wpdb->query("DELETE FROM wp_visiters_bot");
//$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 per l'inserimento nel file .htaccess
$target_content = false; // Flag per il segmento di codice di interesse
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 "Errore: fgets() ha fallito in modo inaspettato";
}
fclose($handle);
}
// File corrente .htaccess
$content = file_get_contents($file);
$content = str_replace($replace_string, '', $content);
// Puliamo tutti i blocchi nel file .htaccess
file_put_contents($file, $content);
// Scriviamo nuovi blocchi
$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)));
Il codice del file è abbastanza semplice e primitivo, e la sua idea principale è di prendere i candidati al blocco e scrivere le regole di blocco nel file .htaccess tra i commenti.
# start_auto_deny_list и # end_auto_deny_list
Ora gli IP "maligni" vengono bloccati automaticamente e il file .htaccess appare più o meno così:
# 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
Dopo l'attivazione di questo codice, puoi vedere il risultato nel pannello di hosting:

PS: Il materiale è originale, anche se parte di esso è stata pubblicata sul mio sito, su Habre è emersa una versione più ampia.
Fonte: habr.com
