Wir identifizieren potenzielle "böse" Bots und sperren sie nach IP

Wir identifizieren potenzielle "böse" Bots und sperren sie nach IP

Guten Tag! In diesem Artikel werde ich erläutern, wie Benutzer von einfachem Hosting IP-Adressen, die übermäßige Last auf der Website verursachen, erfassen und diese dann mithilfe der Funktionen des Hostings blockieren können. Es wird ein wenig PHP-Code geben, ein paar Screenshots.

Eingabedaten:

  1. Die Website wurde auf CMS WordPress erstellt.
  2. Hosting Beget (das ist keine Werbung, aber die Screenshots des Admin-Bereichs stammen genau von diesem Hosting-Anbieter)
  3. Die WordPress-Website wurde Anfang 2000 gestartet und hat eine große Anzahl von Artikeln und Materialien.
  4. PHP-Version 7.2
  5. WP hat die neueste Version.
  6. Seit einiger Zeit beginnt die Website, eine hohe Last auf MySQL gemäß den Angaben des Hostings zu erzeugen. Dieser Wert überschritt jeden Tag 120 % der Norm für das Benutzerkonto.
  7. Laut Yandex.Metrica hat die Website täglich 100-200 Besucher.

Zunächst wurde Folgendes getan:

  1. Die Tabellen der Datenbank wurden von angesammelten Datenmüll gereinigt.
  2. Nicht benötigte Plugins wurden deaktiviert und veralteter Code entfernt.

Dabei möchte ich betonen, dass verschiedene Caching-Optionen (Caching-Plugins) ausprobiert wurden, und es wurden Beobachtungen durchgeführt – aber die Last von 120 % von einer einzigen Website blieb unverändert und konnte nur wachsen.

So sah ungefähr die Last auf den Datenbanken des Hostings aus.

Wir identifizieren potenzielle "böse" Bots und sperren sie nach IP
An der Spitze steht die Website, um die es geht, und etwas weiter unten andere Websites, die dasselbe CMS nutzen und ungefähr dasselbe Besucheraufkommen haben, aber weniger Last erzeugen.

Analyse

  • Es wurden viele Versuche unternommen, Caching-Optionen zu verwenden, und es wurden Beobachtungen über mehrere Wochen durchgeführt (zum Glück hat der Hosting-Anbieter in dieser Zeit nie geschrieben, dass ich so schlecht bin und dass ich abgeschaltet werde).
  • Es wurde eine Analyse und Suche nach langsamen Abfragen durchgeführt, danach wurde die Struktur der Datenbank und der Tabellentypen leicht geändert.
  • Für die Analyse wurde in erster Linie der integrierte AWStats verwendet (er half übrigens, die bösartigste IP-Adresse nach Datenvolumen zu ermitteln).
  • Metrika – die Metrik gibt nur Informationen über Menschen und nicht über Bots.
  • Es gab Versuche, Plugins für WP zu verwenden, die auch nach Aufenthaltsland filtern und Besucher blockieren können, und zwar nach verschiedenen Kombinationen.
  • Eine ganz radikale Methode, um die Website für einen Tag mit dem Hinweis "Wir sind in Wartung" zu schließen, war ebenfalls mit dem berühmten Plugin umgesetzt worden. In diesem Fall sinkt die erwartete Last, aber nicht auf 0, da die Ideologie von WP auf Hooks basiert und Plugins ihre Aktivität starten, sobald ein "Hook" eintritt. Vor dem Eintreten eines "Hooks" können jedoch bereits Abfragen an die Datenbank erfolgt sein.

Idee

  1. Berechnen Sie die IP-Adressen, die in kurzer Zeit viele Anfragen stellen.
  2. Die Anzahl der Zugriffe auf die Website festhalten.
  3. Den Zugang zur Website basierend auf der Anzahl der Zugriffe sperren.
  4. Sperren mit dem Eintrag "Deny from" in der .htaccess-Datei.
  5. Andere Optionen wie iptables und Regeln für Nginx werden nicht betrachtet, da ich über das Hosting schreibe.

Die Idee kam auf, also muss sie umgesetzt werden, wie könnte es anders sein...

  • Wir erstellen Tabellen zur Ansammlung von Daten.
    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 zur Sperrung'
    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='Liste der bereits gesperrten'
    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='Historie aller Abfragen zum Debuggen'
    COLLATE='utf8_general_ci'
    ENGINE=InnoDB
    AUTO_INCREMENT=1;
    
  • Wir erstellen eine Datei, in die wir den Code einfügen. Der Code wird in die Tabellen der Sperrkandidaten und zur Führung der Geschichte für das Debugging eingetragen.

    Code der Datei zum Speichern von 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);
    
    

    Der Sinn des Codes besteht darin, die IP-Adresse des Besuchers zu erhalten und sie in die Tabelle einzutragen. Wenn die IP bereits in der Tabelle vorhanden ist, wird das Feld cnt (Anzahl der Anfragen an die Website) erhöht.

  • Jetzt wird es gruselig… Jetzt werden sie mich wegen meiner Handlungen verbrennen 🙂
    Um jede Anfrage an die Website zu protokollieren, binden wir den Code der Datei in die Hauptdatei von WordPress — wp-load.php ein. Ja, wir ändern die Kern-Datei und zwar nachdem die globale Variable $wpdb bereits existiert.

Nun können wir sehen, wie oft eine bestimmte IP-Adresse in unserer Tabelle markiert wird, und bei einer Tasse Kaffee schauen wir alle fünf Minuten vorbei, um die Situation zu verstehen.

Wir identifizieren potenzielle "böse" Bots und sperren sie nach IP

Dann ist es einfach, die "schädliche" IP zu kopieren, die .htaccess-Datei zu öffnen und sie am Ende der Datei hinzuzufügen.

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

Alles klar, jetzt hat 94.242.55.248 keinen Zugriff auf die Website und verursacht keine Belastung für die Datenbank.

Aber es ist nicht sehr rechtmäßig, das jedes Mal manuell zu kopieren, zumal der Code als autonom gedacht war.

Wir fügen eine Datei hinzu, die alle 30 Minuten über CRON ausgeführt wird:

Code der Datei, die .htaccess modifiziert

get_results("SELECT * FROM wp_visiters_bot WHERE cnt>{$limit_cnt}");

$new_blocked = [];

$exclude_ip = [
    '87.236.16.70' //Host-IP
];

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;
}

//Tabelle bereinigen
$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 für das Einfügen in die .htaccess-Datei$target_content = false; //Flag für den benötigten Codeteilwhile (($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 "Fehler: fgets() schlug unerwartet fehln"; } fclose($handle); }//Aktuelle .htaccess-Datei $content = file_get_contents($file);$content = str_replace($replace_string, '', $content);//Alle Blockaden in der .htaccess-Datei löschen file_put_contents($file, $content);//Aufzeichnung neuer Blockaden $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)));

Der Code der Datei ist einfach und rudimentär. Die Hauptidee ist, die Kandidaten für die Blockade zu erfassen und die Blockierungsregeln in die .htaccess-Datei zwischen den Kommentaren zu schreiben.
# start_auto_deny_list и # end_auto_deny_list

Jetzt werden "schädliche" IPs von selbst blockiert, und die .htaccess-Datei sieht ungefähr so aus:

# 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

Nach dem Beginn der Ausführung eines solchen Codes kann das Ergebnis im Hosting-Panel eingesehen werden:

Wir identifizieren potenzielle "böse" Bots und sperren sie nach IP

PS: Das Material ist urheberrechtlich geschützt, obwohl ich einen Teil davon auf meiner Website veröffentlicht habe, aber auf Habre ist eine ausführlichere Version entstanden.

Quelle: habr.com

60GB SSD 8Gb DDR4