Good day! In the article I’ll tell you how it is possible for ordinary hosting users to catch IP addresses generating excessive load on the site and then block them using hosting tools, there will be a “little bit” php code, a few screenshots.
Inputs:
- Website created on CMS WordPress
- Hosting Beget (this is not an advertisement, but the screenshots of the admin panel will be of this hosting provider)
- WordPress site launched somewhere in the beginning of 2000 and has a large number of articles and materials.
- PHP Version 7.2
- WP has the latest version
- For some time now the site began to generate a high load on MySQL according to the hosting. Every day, this value exceeded 120% of the norm per account
- According to Yandex. The metric site is visited by 100-200 people a day
First of all, it was done:
- Database tables are cleared of accumulated garbage
- Unnecessary plugins are disabled, sections of outdated code are removed
At the same time, I draw attention to the caching options (caching plugins), we made observations - but the load of 120% from one site was unchanged and could only grow.
How the approximate load on the hosting databases looked
In the top is the site in question, just below other sites that have the same cms and about the same traffic, but create less load.
Analysis
- Many attempts were made with options for caching data, observations were made for several weeks (the benefit of hosting during this time I have never written that I am so bad and will disconnect me)
- There was an analysis and search for slow queries, then the database structure and table type were slightly changed
- For analysis, the built-in AWStats was primarily used (by the way, it helped to calculate the most evil IP address according to the volume of traffic
- Metric - the metric gives information only about people, not about bots
- There were attempts to use plugins for WP, which can filter and block visitors even by country of location and by various combinations
- It turned out to be a completely radical way to close the site for a day marked “We are on maintenance” - this was also done with the help of the famous plug-in. In this case, we expect the load to drop, but not to 0-left values, since the ideology of WP is based on hooks and plugins begin their activity when some kind of “hook” occurs, and before the “hook” occurs, queries to the database can already be made
Idea
- Calculate IP addresses that make a lot of requests in a short period of time.
- Record the number of hits to the site
- Based on the number of hits block access to the site
- Block using the “Deny from” entry in the .htaccess file
- Other options, like iptables and rules for Nginx without considering, because I write about hosting
An idea came up, so you need to realize how without it ...
- We create tables for data accumulation
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;
- Create a file in which we will place the code. The code will be written in the table of candidates for locking and keep a history for debug.
File code for recording IP addresses<?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);
The essence of the code is to get the visitor's IP address and write it to the table. If ip is already in the table, the cnt field will be increased (the number of requests to the site) - Now it's scary ... Now they will burn me for my actions :)
To record every call to the site, we include the file code in the main WordPress file - wp-load.php. Yes, we modify the kernel file precisely after the global $ wpdb variable already exists
So, now we can see how often this or that IP address is noted in our table and with a coffee mug we look there every 5 minutes to understand the picture
Then simply copy the “harmful” IP, open the .htaccess file and add it to the end of the file
Order allow,deny Allow from all # start_auto_deny_list Deny from 94.242.55.248 # end_auto_deny_list
Everything, now 94.242.55.248 - does not have access to the site and does not generate a load on the database
But each time, copying with your hands is not a very righteous task, and besides, the code was conceived as standalone
Add a file that will be executed by CRON every 30 minutes:
File Code Modifying .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)));
The code of the file is quite simple and primitive and its main idea is to take the candidates for blocking and enter the blocking rules into the .htaccess file between comments
# start_auto_deny_list and # end_auto_deny_list
Now the "harmful" ip are blocked by themselves, and the .htaccess file looks something like this:
# 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
As a result, after the beginning of the action of such code, you can see the result in the hosting panel:
PS: The material is copyright, even though I published part of it on my website, but on Habre a more extended version was obtained.