Εντοπίζουμε πιθανά «κακά» bots και τα αποκλείουμε μέσω IP

Εντοπίζουμε πιθανά «κακά» bots και τα αποκλείουμε μέσω IP

Καλή μέρα! Στο άρθρο θα σας πω πώς οι χρήστες της κανονικής φιλοξενίας μπορούν να πιάσουν διευθύνσεις IP που δημιουργούν υπερβολικό φορτίο στον ιστότοπο και στη συνέχεια να τις αποκλείσουν χρησιμοποιώντας εργαλεία φιλοξενίας, θα υπάρχει "λίγος" κώδικας php, μερικά στιγμιότυπα οθόνης.

Εισαγωγή δεδομένων:

  1. Ιστότοπος που δημιουργήθηκε σε CMS WordPress
  2. Φιλοξενία Beget (αυτή δεν είναι διαφήμιση, αλλά τα στιγμιότυπα οθόνης του πίνακα διαχείρισης θα προέρχονται από αυτόν τον συγκεκριμένο πάροχο φιλοξενίας)
  3. Ο ιστότοπος WordPress ξεκίνησε κάπου στις αρχές του 2000 και έχει μεγάλο αριθμό άρθρων και υλικού
  4. PHP έκδοση 7.2
  5. Το WP έχει την πιο πρόσφατη έκδοση
  6. Εδώ και αρκετό καιρό, ο ιστότοπος άρχισε να δημιουργεί υψηλό φορτίο στη MySQL σύμφωνα με τα δεδομένα φιλοξενίας. Κάθε μέρα αυτή η τιμή ξεπερνούσε το 120% του κανόνα ανά λογαριασμό
  7. Σύμφωνα με το Yandex. Την τοποθεσία Metrica επισκέπτονται 100-200 άτομα την ημέρα

Πρώτα απ 'όλα, έγινε αυτό:

  1. Οι πίνακες βάσεων δεδομένων καθαρίστηκαν από τα συσσωρευμένα σκουπίδια
  2. Οι περιττές προσθήκες απενεργοποιήθηκαν, οι ενότητες του παλιού κώδικα αφαιρέθηκαν

Ταυτόχρονα, θα ήθελα να επιστήσω την προσοχή σας στο γεγονός ότι δοκιμάστηκαν επιλογές προσωρινής αποθήκευσης (προσθήκες προσωρινής αποθήκευσης), έγιναν παρατηρήσεις - αλλά το φορτίο 120% από έναν ιστότοπο παρέμεινε αμετάβλητο και μπορούσε μόνο να αυξηθεί.

Πώς φαινόταν το κατά προσέγγιση φορτίο στις βάσεις δεδομένων φιλοξενίας

Εντοπίζουμε πιθανά «κακά» bots και τα αποκλείουμε μέσω IP
Στην κορυφή βρίσκεται ο εν λόγω ιστότοπος, ακριβώς από κάτω υπάρχουν άλλοι ιστότοποι που έχουν τα ίδια cms και περίπου την ίδια επισκεψιμότητα, αλλά δημιουργούν λιγότερο φορτίο.

Ανάλυση

  • Έγιναν πολλές προσπάθειες με επιλογές προσωρινής αποθήκευσης δεδομένων, πραγματοποιήθηκαν παρατηρήσεις για αρκετές εβδομάδες (ευτυχώς, κατά τη διάρκεια αυτής της περιόδου η φιλοξενία δεν μου έγραψε ποτέ ότι ήμουν τόσο κακός και θα αποσυνδεθώ)
  • Έγινε ανάλυση και αναζήτηση για αργά ερωτήματα, στη συνέχεια η δομή της βάσης δεδομένων και ο τύπος του πίνακα άλλαξαν ελαφρώς
  • Για ανάλυση, χρησιμοποιήσαμε κυρίως τα ενσωματωμένα AWStats (παρεμπιπτόντως, βοήθησε στον υπολογισμό της χειρότερης διεύθυνσης IP με βάση τον όγκο επισκεψιμότητας
  • Μέτρηση - η μέτρηση παρέχει πληροφορίες μόνο για άτομα, όχι για bots
  • Έχουν γίνει προσπάθειες χρήσης προσθηκών για WP που μπορούν να φιλτράρουν και να αποκλείουν τους επισκέπτες ακόμη και ανά χώρα τοποθεσίας και διάφορους συνδυασμούς
  • Ένας εντελώς ριζοσπαστικός τρόπος αποδείχθηκε ότι ήταν να κλείσετε τον ιστότοπο για μια μέρα με τη σημείωση "Είμαστε υπό συντήρηση" - αυτό έγινε επίσης χρησιμοποιώντας το διάσημο πρόσθετο. Σε αυτήν την περίπτωση, αναμένουμε να πέσει το φορτίο, αλλά όχι σε μηδενικές τιμές, καθώς η ιδεολογία του WP βασίζεται σε άγκιστρα και τα πρόσθετα ξεκινούν τη δραστηριότητά τους όταν εμφανίζεται ένα "hook" και πριν εμφανιστεί το "hook", τα αιτήματα στη βάση δεδομένων μπορούν έχει ήδη γίνει

Ιδέα

  1. Υπολογίστε διευθύνσεις IP που κάνουν πολλά αιτήματα σε σύντομο χρονικό διάστημα.
  2. Καταγράψτε τον αριθμό των επισκέψεων στον ιστότοπο
  3. Αποκλεισμός πρόσβασης στον ιστότοπο με βάση τον αριθμό των επισκέψεων
  4. Αποκλεισμός χρησιμοποιώντας την καταχώρηση "Απόρριψη από" στο αρχείο .htaccess
  5. Δεν σκέφτηκα άλλες επιλογές, όπως iptables και κανόνες για το Nginx, επειδή γράφω για τη φιλοξενία

Μια ιδέα εμφανίστηκε, άρα πρέπει να εφαρμοστεί, καθώς χωρίς αυτό...

  • Δημιουργία πινάκων για τη συγκέντρωση δεδομένων
    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;
    
  • Ας δημιουργήσουμε ένα αρχείο στο οποίο θα τοποθετήσουμε τον κώδικα. Ο κώδικας θα εγγραφεί στους πίνακες υποψηφίων αποκλεισμού και θα κρατήσει ένα ιστορικό για εντοπισμό σφαλμάτων.

    Κωδικός αρχείου για την εγγραφή διευθύνσεων 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);
    
    

    Η ουσία του κώδικα είναι να πάρει τη διεύθυνση IP του επισκέπτη και να την γράψει σε έναν πίνακα. Εάν η ip βρίσκεται ήδη στον πίνακα, το πεδίο cnt θα αυξηθεί (ο αριθμός των αιτημάτων στον ιστότοπο)

  • Τώρα το τρομακτικό... Τώρα θα με κάψουν για τις πράξεις μου :)
    Για να καταγράψουμε κάθε αίτημα στον ιστότοπο, συνδέουμε τον κώδικα του αρχείου στο κύριο αρχείο του WordPress - wp-load.php. Ναι, αλλάζουμε το αρχείο του πυρήνα και ακριβώς αφού υπάρχει ήδη η καθολική μεταβλητή $wpdb

Έτσι, τώρα μπορούμε να δούμε πόσο συχνά σημειώνεται αυτή ή η άλλη διεύθυνση IP στο τραπέζι μας και με μια κούπα καφέ κοιτάμε εκεί μια φορά κάθε 5 λεπτά για να καταλάβουμε την εικόνα

Εντοπίζουμε πιθανά «κακά» bots και τα αποκλείουμε μέσω IP

Στη συνέχεια, απλώς αντιγράψτε την "επιβλαβή" IP, ανοίξτε το αρχείο .htaccess και προσθέστε το στο τέλος του αρχείου

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

Αυτό ήταν, τώρα 94.242.55.248 - δεν έχει πρόσβαση στον ιστότοπο και δεν δημιουργεί φορτίο στη βάση δεδομένων

Αλλά κάθε φορά που η αντιγραφή με το χέρι όπως αυτό δεν είναι μια πολύ σωστή δουλειά, και επιπλέον, ο κώδικας προοριζόταν να είναι αυτόνομος

Ας προσθέσουμε ένα αρχείο που θα εκτελείται μέσω CRON κάθε 30 λεπτά:

Κώδικας αρχείου που τροποποιεί .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)));

Ο κώδικας του αρχείου είναι αρκετά απλός και πρωτόγονος και η βασική του ιδέα είναι να πάρει υποψηφίους για αποκλεισμό και να εισαγάγει κανόνες αποκλεισμού στο αρχείο .htaccess μεταξύ των σχολίων
# start_auto_deny_list και # end_auto_deny_list

Τώρα οι "επιβλαβείς" IP αποκλείονται από μόνες τους και το αρχείο .htaccess μοιάζει κάπως έτσι:

# 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

Ως αποτέλεσμα, αφού αυτός ο κώδικας αρχίσει να λειτουργεί, μπορείτε να δείτε το αποτέλεσμα στον πίνακα φιλοξενίας:

Εντοπίζουμε πιθανά «κακά» bots και τα αποκλείουμε μέσω IP

ΥΓ: Το υλικό είναι του συγγραφέα, αν και δημοσίευσα μέρος του στην ιστοσελίδα μου, έλαβα μια πιο διευρυμένη έκδοση στο Habre.

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο