Devops's Blog

HAproxy боремся с ботами

1.Защита мобильного приложения
Трафик мобильного приложения нельзя фильтровать по ip , так как за ip может быть несколько устрйств, значит делим трафик мобильного приложния на x-device

Исходные данные
mobile.api.ru - мобильный домен
request: /mobile/search
method: post - метод запроса
				    

global
localpeer                   localpeers

peers traffic_ext1
   bind *:10001
   server localpeers

acl x-device_found   hdr_cnt(x-device) eq 1					    
http-request set-header xbots1 %[req.fhdr(x-device)]_%[req.fhdr(ip-geo)] if x-device_found { hdr(host) -i mobile.api.ru }
http-request track-sc0 hdr(xbots1) table bruteforce_ext_1h_1 if x-device_found { hdr(host) -i mobile.api.ru } 
http-request sc-inc-gpc0(0) if METH_POST { path /mobile/search  } { hdr(host) -i mobile.api.ru }
http-request deny deny_status 429 if { sc_gpc0_rate(0,bruteforce_ext_1h_1) ge 2 }
http-request deny deny_status 429 if x-device_empty { hdr(host) -i mobile.api.ru } 
http-request del-header xbots1

backend bruteforce_ext_1h_1
    stick-table type string len 200 size 1m expire 6s store gpc0,gpc1,gpc0_rate(6s),gpc1_rate(6s),http_req_rate(6s) peers traffic_ext1


					     
acl x-device_found определяет нахождение x-device в трафике. Создаем хедер xbots1 в котором определяем связку x-device и geo(если есть) и вставляем наш хедер в домен mobile.api.ru при условии , что есть x-device_found. Опеределем динамическую таблицу bruteforce_ext_1h_1 которая обновляем трафик каждые 6sec(столько времени приложения обрабатывает данный запрос). Внутри таблицы опеределяем счетчик gpc0 который считвает кол-во наших запросов. Превым deny блочим тех кто сделал больше или равно на 2 запросом , блок на 6sec. Вторым deny блочим всех кто пришел без x-device. И traffic_ext1 нужен чтоб таблица не сбрасывалась при reload haproxy. Удаляем хедер xbots1

2.Защита сайта при прохождения правильного flow

Исходные данные
profile.ru - домен
request: /search1 /search2 /search3 /search4 /search5

global
localpeer                   localpeers

peers traffic_ext2
   bind *:10002
   server localpeers

acl ext_ip hdr_ip(x-forwarded-for,-1) -m found
http-request set-header xbots2 %[hdr_ip(X-Forwarded-For,-1)]_%[req.fhdr(ip-geo)] if ext_ip { hdr(host) -i profile.ru }
http-request track-sc1 hdr(xbots2) table bruteforce_ext_1h_2 if ext_ip { hdr(host) -i profile.ru }
http-request sc-inc-gpc0(1) if { path -i /search1 } || { path -i /search2 }
http-request sc-inc-gpc1(1) if { path -i /search3 } || { path_beg -i /search4 } || { path_beg -i /search5 }
http-request deny deny_status 429 if ext_ip { sc_gpc0_rate(1,bruteforce_ext_1h_2) ge 1 } { sc_gpc1_rate(1,bruteforce_ext_1h_2) eq 0 }
http-request del-header xbots2 if ext_ip

backend bruteforce_ext_1h_2
    stick-table type string len 200 size 1m expire 20m store gpc0,gpc1,gpc0_rate(20m),gpc1_rate(20m),http_req_rate(20m) peers traffic_ext2

 
acl ext_ip определяет нахождение внешнего вдреса в трафике, если трафик за одним прокси. Создаем хедер xbots2 в котором определяем связку внешнего вдреса и geo(если есть) и вставляем наш хедер в домен profile.ru при условии , трафик внешний. Опеределем динамическую таблицу bruteforce_ext_1h_2 которая обновляем трафик каждые 20min(опеределено тестами).Внутри таблицы опеределяем счетчик gpc0 и gpc1 который считвает кол-во наших запросов. В deny блочим тех кто сделал больше или равно на 1 запрос в /search1 или /search2 , но при этом ни разу не сходил в /search3 или /search4 или /search5 , блок на 20min. Удаляем хедер xbots2

3.Ставим капчу на сайт

Исходные данные:
php,php-fpm: уже установлены
cap.ru - домен
request: /search
капчу взял описание исходники

Правим фаил process-form.php , добавляем отдачу куки x-bots при правильном прохождении капчи

if (empty($code)) {
  $result['errors'][] = ['captcha', 'Пожалуйста введите код!'];
} else {
  $code = crypt(trim($code), '$1$nn34GFH65g4jojn4');
  $result['success'] = $captcha === $code;
  //Здесь вставляем куку
  if ($result['success']) { setcookie('x-bots', '1qazxsw2', time() + 60, '/', 'cap.ru'); $_COOKIE['x-bots'] = '1qazxsw2';  }
  if (!$result['success']) {
    $result['errors'][] = ['captcha', 'Введенный код не соответствует изображению!'];
  }
}
 
Правим index.php , добавим редирект сам на себя , но уже с кукой , если пройдена капча

              document.querySelector('.form-result').classList.remove('d-none');
              //Здесь вставляем редирект
              window.location.replace(window.location.href);
 
Сам конфиг haproxy, ключевое здесь это проверка на куку "1qazxsw2"

acl hdr_dmn      hdr_beg(host) -i  cap.ru
acl path_1       path_beg      -i /assets/php/
acl path_2       path          -i /search

use_backend bknd_captcha  if hdr_dmn path_1
use_backend bknd_captcha  if hdr_dmn path_2 !{ hdr(cookie) -m sub "1qazxsw2" }
use_backend bknd_default  if hdr_dmn path_2


backend bknd_captcha
    balance source
    option allbackups
    timeout server 30s
    option http-server-close
    use-fcgi-app php-fpm
    acl path_1       path          -i /search    
    http-request set-path / if path_1
    server php-fpm 127.0.0.1:4141 proto fcgi check port 4141 inter 3000

fcgi-app php-fpm
    log-stderr global
    docroot /opt/web/captcha
    index index.php

backend bknd_default
     balance     leastconn
     server  server.ru server.ru:443 check ssl verify none inter 4500