Identificamos bots "maliciosos" potenciais e os bloqueamos por IP

Identificamos bots "maliciosos" potenciais e os bloqueamos por IP

Bom dia! Neste artigo, vou explicar como os usuários de hospedagem comum podem capturar os endereços IP que geram carga excessiva no site e depois bloqueá-los usando as ferramentas de hospedagem. Serão apresentados alguns trechos de código PHP e várias capturas de tela.

Dados básicos:

  1. Site criado na CMS WordPress
  2. Alojamento Beget (isto não é um anúncio, mas as capturas de tela do painel de administração serão deste provedor de hospedagem)
  3. O site WordPress foi iniciado no início dos anos 2000 e possui uma grande quantidade de artigos e materiais
  4. Versão PHP 7.2
  5. A WP está na versão mais recente
  6. Recentemente, o site começou a gerar alta carga no MySQL, segundo o provedor de hospedagem. A carga diária ultrapassava 120% do normal para a conta
  7. De acordo com Yandex.Metrica, o site recebe entre 100-200 visitantes por dia

Em primeiro lugar, foram realizadas as seguintes ações:

  1. As tabelas do banco de dados foram limpas do lixo acumulado
  2. Desativados os plugins desnecessários e removidos trechos de código obsoleto

Vale ressaltar que foram testadas opções de cache (plugins de cache), foram feitas observações, mas a carga de 120% de um único site permaneceu inalterada e poderia apenas aumentar.

Assim estava o exemplo de carga nos bancos de dados do provedor de hospedagem

Identificamos bots "maliciosos" potenciais e os bloqueamos por IP
No topo está o site em questão, logo abaixo outros sites que possuem a mesma CMS e aproximadamente a mesma taxa de visitas, mas geram menos carga.

Análise

  • Foram feitas várias tentativas com diferentes opções de caching, e as observações se prolongaram por várias semanas (felizmente o provedor de hospedagem não me avisou nenhuma vez que eu era um problema e que poderia ser desconectado)
  • Foi feita uma análise e busca de consultas lentas, e a estrutura do banco de dados e o tipo de tabelas foram ligeiramente alterados
  • Para a análise, foi utilizado, em primeiro lugar, o AWStats embutido (que, a propósito, ajudou a identificar o IP mais problemático baseado no volume de tráfego
  • As métricas fornecem informações apenas sobre pessoas, e não sobre bots
  • Tentativas foram feitas para usar plugins para WP que conseguem filtrar e bloquear visitantes até mesmo com base no país de origem e em várias combinações.
  • Uma maneira radical de fechar o site por um dia com a mensagem 'Estamos em manutenção' foi feita com a ajuda do famoso plugin. Nesse caso, a carga esperada caiu, mas não para valores de zero, já que a ideologia do WP se baseia em hooks e os plugins começam sua atividade assim que um ‘hook’ é acionado, e antes do acionamento do ‘hook’ já podem ter sido feitas consultas ao banco de dados.

Ideia

  1. Calcular os endereços IP que fazem muitas solicitações em um curto espaço de tempo.
  2. Registrar o número de acessos ao site
  3. Com base no número de acessos, bloquear o acesso ao site
  4. Bloquear usando a gravação 'Deny from' no arquivo .htaccess
  5. Outras opções, como iptables e regras para Nginx, não serão consideradas, pois escrevo sobre hospedagem

Apareceu uma ideia, então precisamos implementá-la, como viver sem isso...

  • Criamos tabelas para acumular dados
    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='Candidatos para bloqueio'
    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='Lista já bloqueada'
    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='Histórico de todas as solicitações para depuração'
    COLLATE='utf8_general_ci'
    ENGINE=InnoDB
    AUTO_INCREMENT=1;
    
  • Vamos criar um arquivo para o qual colocaremos o código. O código irá registrar na tabela os candidatos ao bloqueio e manter um histórico para depuração.

    Código do arquivo para registrar endereços 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);
    
    

    A essência do código é obter o endereço IP do visitante e registrá-lo na tabela. Se o IP já existir na tabela, o campo cnt (número de solicitações ao site) será incrementado.

  • Agora vem a parte difícil... Agora vão me queimar por minhas ações 🙂
    Para registrar cada acesso ao site, conectamos o código do arquivo no arquivo principal do WordPress — wp-load.php. Sim, estamos alterando um arquivo central e apenas depois que a variável global $wpdb já existe.

Agora, podemos ver com que frequência cada endereço IP é marcado em nossa tabela e, com uma xícara de café, damos uma olhada a cada 5 minutos para compreender o cenário.

Identificamos bots "maliciosos" potenciais e os bloqueamos por IP

Em seguida, é simples: copiamos o IP 'malicioso', abrimos o arquivo .htaccess e adicionamos ao final do arquivo.

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

Pronto, agora 94.242.55.248 não tem acesso ao site e não gera carga no banco de dados.

Mas copiar manualmente toda vez não é uma tarefa muito justa, e além disso, o código foi pensado para ser autônomo.

Vamos adicionar um arquivo que será executado pelo CRON a cada 30 minutos:

Código do arquivo que modifica o .htaccess

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

$new_blocked = [];

$exclude_ip = [
    '87.236.16.70' // Endereço de hospedagem
];

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

// Limpeza da tabela
$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 = ''; // Teste para inserção no arquivo .htaccess$target_content = false; // Flag para o segmento de código desejadowhile (($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 "Erro: fgets() falhou inesperadamente\n"; } fclose($handle); }// Arquivo .htaccess atual $content = file_get_contents($file);$content = str_replace($replace_string, '', $content);// Limpamos todos os bloqueios no arquivo .htaccess file_put_contents($file, $content);// Gravação de novos bloqueios $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)));

O código do arquivo é bastante simples e primitivo, e sua principal ideia é pegar os candidatos ao bloqueio e registrar as regras de bloqueio no arquivo .htaccess entre os comentários.
# start_auto_deny_list и # end_auto_deny_list

Agora, os IPs 'maliciosos' são bloqueados automaticamente, e o arquivo .htaccess se parece mais ou menos com isto:

# 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

Como resultado, após o início da execução desse código, você pode ver o resultado no painel de hospedagem:

Identificamos bots "maliciosos" potenciais e os bloqueamos por IP

PS: O material é autoral, embora parte dele eu tenha publicado em meu site, mas na Habre saiu uma versão mais extensa.

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster