Как мы боролись с парсерами

от автора

image
Ключевые моменты:
* Реализация скрипта для проверки PTR посетителей;
* Конфигурирование nginx в IfIsEvil-style с ветвлениями map;
* Имена location в переменных map;
* Управление ветвлением через try_files /nonexist $map_var.

Многие высоконагруженные и популярные сайты страдают от того, что кроме живых посетителей их посещают разнообразные парсеры, боты и прочие автоматические сканеры, которые не несут никакого полезного эффекта, а только создают паразитный трафик и нагрузку на, и без того, нагруженную систему. В данном случае я не имею виду поисковых ботов, которые хоть и зачастую нагружают проект не нормировано, но просто необходимы любому проекту.
Один из наших клиентов регулярно испытывал проблему лавинообразного роста нагрузки в определенное время суток. Периодически, раз в сутки и чаще происходили наплывы посещений со значительным ростом LA на серверах. Было принято решение построить защиту от паразитного трафика.

Мы обнаружили, что у паразитного трафика имеются определенные паттерны поведения и свойства, а именно:

* По источнику запросов – подсети Amazon, Tor;
* По точкам входа – большинство запросов к разделу товаров;
* По UserAgent – основная часть ботов отправляли UA поисковиков Google, Yandex, Bing, но объективно не являлись ими;
* По рефереру – в основном реферер был пустой.

Реализованные проверки, ограничения и блокировки:

* Вручную добавили заранее известные IP в белый список
* И ранее скомпрометированные IP – в черный список
* Ограничение limit_req_zone для всех, кроме белых списков
* Проверка посетителей с UA поисковиков на соответствие PTR-записи настоящим PTR-записям поисковиков и помещаем в белый список прошедших проверку и всегда разрешаем их.
* При повышении LA порога «Атака»:
** Не прошедших проверку UA по PTR мы заносим в черный список и блокируем.
** Проверяем повторяемость рефереров в access-логе и блокируем посетителей с реферером, превысившим заданное количество вхождений
** Остальных проверяем капчей и разрешаем в случае успеха.

Первые три пункта это распространенные решения, поэтому подробнее остановлюсь на на проверке посетителей по PTR, конфигурировании nginx c использованием конструкции try_files /nonexist $map_var и сложных map.

Мы реализовали скрипт для асинхронной проверки посетителей с UA поисковиков на соответствие PTR-записи настоящим PTR-записям поисковиков.

Он запускается по cron раз в минуту. По уникализированному списку IP посетителей с UA поисковика производит проверку PTR и сверяет доменное имя второго уровня. Если домен совпадает, то добавляет IP в белый список, иначе в черный список. При проверке списка, скрипт не проверяет PTR уже проверенных ранее IP для ускорения процесса проверки. Это позволяет проходить по списку IP из access-лога ежеминутно даже при высокой скорости наполнения access-лога. Записи в черный список заносятся с указанием фиксированного времени удаления из списка блокировки для исключения постоянной блокировки общих IP в больших NAT сетях.

Таким образом мы формируем и поддерживаем файлы ptr_blacklist.map и ptr_whitelist.map которые инклудятся в конфиг nginx.

Запускается ежеминутно.

Листинг скрипта проверки соответствия UA и PTR:

#!/bin/bash  # Базовые настройки скрипта, обычно выносятся в отдельный файл, # чтобы их можно было подстраивать под конкретный проект, не меняя основной скрипт. # Export inc file for nginx EXPORT_MAP=true  # Domain list DOMAIN_LIST="domain"  # Block time (in minutes) BLOCK_TIME=1440  # White list IP IP_WHITELIST=""  # White list PTR BOTS="google|yandex|bing|Bing|msn"  # false - not block IP if there is a PTR record BLOCK_WITH_PTR=true  UNBLOCK_ENABLE=true LOGFILE=/var/log/ua-table.log LOGFILE2=/var/log/ua-table-history.log LOCK=/tmp/ua_check.lock  D=$DOMAIN_LIST  # Скрипт формирует ptr_blacklist и ptr_whitelist и только потом копирует их в map-файлы # для минимизации блокировки рабочих файлов BL_FILE=/etc/nginx/vhosts.d/ptr_blacklist WL_FILE=/etc/nginx/vhosts.d/ptr_whitelist BL_FILE_MAP=$BL_FILE.map WL_FILE_MAP=$WL_FILE.map  TMP_LOG=/tmp/$D-acc-temp.log TMP_LOG1=/tmp/$D-acc-temp1.log NGINX_LOG=/srv/www/$D/shared/log/$D-acc.log  [ ! -f /usr/bin/host ] && echo "/usr/bin/host not found. Please yum install bind-utils" && exit [ -z "$DOMAIN_LIST" ] && echo "DOMAIN_LIST is empty" [ ! -f $LOGFILE ] && touch $LOGFILE [ ! -f $LOGFILE2 ] && touch $LOGFILE2  debug="0"  function e {     echo -e $(/bin/date "+%F %T") $1 }  # Проверяем не запущен ли скрипт, это позволяет не дублировать затянувшиеся проверки [ -f $LOCK ] && e "Script $0 is already runing" && exit /bin/touch $LOCK  DT=`/bin/date "+%F %T"`  if [ ! -f $NGINX_LOG ];then     echo "Log ($NGINX_LOG) not found."     /bin/rm -rf $LOCK     exit fi  # Основная часть скрипта  # Делаем выборку из acc-лога, регистр важен, так мы не цепляем записи с вхождением в referer /bin/egrep "Yandex|Google|bingbot|Bing" $NGINX_LOG | /usr/bin/awk '{print $1}' | /bin/sort -n | /usr/bin/uniq > $TMP_LOG   if [ "$EXPORT_MAP" == "true" ]; then     [ ! -f $BL_FILE_MAP ] && /bin/touch $BL_FILE_MAP     [ ! -f $WL_FILE_MAP ] && /bin/touch $WL_FILE_MAP     [ ! -f $BL_FILE ] && /bin/touch $BL_FILE || /bin/cp -f $BL_FILE $BL_FILE.bak     [ ! -f $WL_FILE ] && /bin/touch $WL_FILE || /bin/cp -f $WL_FILE $WL_FILE.bak fi  # Разблокируем адреса UNBLOCK=0 while read line do     if [[ "$line" == *=* ]]; then         GET_TIME=`echo $line | /usr/bin/awk -F"=" '{print $2}'`         NOW=`/bin/date '+%s'`         #echo $NOW         #echo $GET_TIME         if [ "$NOW" -gt "$GET_TIME" ]; then             IP=`echo $line | awk '{print $3}'`             e "$IP unblocked." >> $LOGFILE2             /bin/sed -i '/'$IP'/d' $BL_FILE             /bin/sed -i '/'$IP'/d' $LOGFILE             UNBLOCK=1         #else             #e "Nothing to unblock" >> $LOGFILE2        fi     fi done < $LOGFILE  # Блокируем адреса while read line do     IP=$line     wl=0     bl=0  # входит в ручной WL     for I in $IP_WHITELIST     do         if [ "$I" = "$IP" ];then             wl=1         fi     done  # ранее уже проверен и внесен в WL     for I in $(/usr/bin/awk '{print $1}' < "$WL_FILE" )     do         if [ "$I" = "$IP" ];then             wl=1         fi     done  # ранее уже проверен и внесен в BL     for I in $(/usr/bin/awk '{print $1}' < "$BL_FILE" )     do         if [ "$I" = "$IP" ];then             bl=1         fi     done  # Если IP есть в списках, значит ранее уже проверен, не проверяем его PTR     if [ "$wl" = "1" -o "$bl" = "1" ]; then        [ "$debug" -gt "1" ] && e "$IP in white or black list" >> $LOGFILE2     else         PTR=""         SRCHBOT=""         FINDPTR="`/usr/bin/host $IP | /bin/grep -v 'not found' | /bin/grep -v 'no PTR record' | /usr/bin/head -1 | /usr/bin/awk '{ print $5 }' | /bin/sed 's/\.$//'`"         if [ -z "$FINDPTR" ];then             PTR=" (PTR record not found)"         else             PTR=" ($FINDPTR)"         fi         SRCHBOT=`/usr/bin/host $IP | /usr/bin/awk '{ print $5 }' | /usr/bin/rev | /usr/bin/cut -d . -f 2-3 | /usr/bin/rev | /bin/egrep "$BOTS"`         [ -n "$SRCHBOT" ] && BOT="YES" || BOT="NO"         [ -z "$BLOCK_WITH_PTR" ] && BLOCK_WITH_PTR=true         if [ "$EXPORT_MAP" == "true" ]; then             if [ "$BOT" == "NO" ]; then                 e "$IP blocked $BLOCK_TIME minutes. ($D) Unblock = `/bin/date --date="$BLOCK_TIME minute" +%s`" >> $LOGFILE                 e "$IP$PTR blocked $BLOCK_TIME minutes. ($D)" >> $LOGFILE2                 echo "$IP 0;" >> $BL_FILE             else                 echo "$IP 1;" >> $WL_FILE             fi         fi     fi done < $TMP_LOG  # часть проверки и подмены map-файлов if [ "$EXPORT_MAP" == "true" ]; then      /bin/sort -u -o $BL_FILE $BL_FILE > /dev/null 2>&1     /bin/sort -u -o $WL_FILE $WL_FILE > /dev/null 2>&1          MAP_CHANGED=0     if ! diff $BL_FILE $BL_FILE.bak > /dev/null 2>&1; then         /bin/cp -f $BL_FILE_MAP $BL_FILE_MAP.bak > /dev/null 2>&1         /bin/cp -f $BL_FILE $BL_FILE_MAP > /dev/null 2>&1         MAP_CHANGED=1     fi     if ! diff $WL_FILE $WL_FILE.bak > /dev/null 2>&1; then         /bin/cp -f $WL_FILE_MAP $WL_FILE_MAP.bak > /dev/null 2>&1         /bin/cp -f $WL_FILE $WL_FILE_MAP > /dev/null 2>&1         MAP_CHANGED=1     fi     if [ "$MAP_CHANGED" -eq "1" -o "$UNBLOCK" -eq "1" ]; then 	RELOAD=`/usr/sbin/nginx -t 2>&1 | /bin/grep ok` 	if [ -n "$RELOAD" ];then    	    /sbin/service nginx reload             e "nginx is reloaded" >> $LOGFILE2 	else     	    ERROR_RELOAD=`/sbin/service nginx configtest 2>&1` 	    /bin/cp -f $BL_FILE_MAP.bak $BL_FILE_MAP > /dev/null 2>&1 	    /bin/cp -f $WL_FILE_MAP.bak $WL_FILE_MAP > /dev/null 2>&1             e "nginx error config test failed" >> $LOGFILE2 	fi      fi fi /bin/rm -rf $LOCK 

Скрипт проверки частоты рефереров и формирование файла referer-block.conf вида:

~domain.ru 0; ~… 1; ~… 1; 

Запускается ежеминутно.

Листинг скрипта проверки частоты рефереров:

#!/bin/bash # referer_protect v.1.0.6  # Базовые настройки скрипта, обычно выносятся в отдельный файл, # чтобы их можно было подстраивать под конкретный проект, не меняя основной скрипт. RECORDS=500 DOMAIN_LIST=domain LA=15 # if Load Average > $LA = Referer is block BLOCK_TIME=360 #in minutes #REF_WHITELIST="" BLOCK_ENABLE=true # true/false - enable/disable add firewall rule. email="mail@mail.ru" LOGFILE=/var/log/referer-table.log LOGFILE2=/var/log/referer-table-history.log LOCK=/tmp/referer.lock MSG_ALERT=/tmp/msg-alert.tmp debug="0"  LA_CURRENT="`cat /proc/loadavg | awk '{ print $1}' | awk 'BEGIN { FS="."; }{ print $1}'`" DT=`date "+%F %T"`  [ ! -f $LOGFILE ] && touch $LOGFILE [ -f "$MSG_ALERT" ] && rm -f $MSG_ALERT  function e {     echo -e $(date "+%F %T") $1 }  function msg {     echo "Referer:$REFERER. Domain:$D" >> $MSG_ALERT }  function send_mail {     if [ "$BLOCK_ENABLE" = "true" -a "$LA_CURRENT" -gt "$LA" ];then 	cat $MSG_ALERT | mailx -s "Referers report. Warning" $email     elif [ "$BLOCK_ENABLE" = "true" -a "$LA_CURRENT" -le "$LA" ];then 	cat $MSG_ALERT | mailx -s "Referers report. Notice " $email     else 	cat $MSG_ALERT | mailx -s "Referers report. Notice (Test mode)" $email     fi }  [ -f $LOCK ] && e "Script $0 is already runing" && exit touch $LOCK  NEED_NGINX_RELOAD=0  for D in $DOMAIN_LIST do      TMP_LOG=/tmp/ddos-$D-acc-referer.log     TMP_AWK=/tmp/tmp_$D-awk.tmp     #NGINX_LOG=/srv/www/$D/logs/$D-acc     NGINX_LOG=/srv/www/$D/shared/log/$D-acc.log     REFCONF=/etc/nginx/referer-block-$D.conf      [ ! -s "$REFCONF" ] && echo "~$D 0;" >> $REFCONF      if [ ! -f $NGINX_LOG ];then         echo "Log ($NGINX_LOG) not found."         /bin/rm -rf $LOCK         exit     fi      tail -10000 $NGINX_LOG | awk '($9 == "200") || ($9 == "404")' | awk '{print $11}' | sort | uniq -c | sort -n | awk -v x=$RECORDS ' $1 > x {print $2} ' > $TMP_LOG     sed -i "s/\"//g" $TMP_LOG # убираем кавычки     sed -i "/^-/d" $TMP_LOG # убираем referer "-"     sed -i "/$D/d" $TMP_LOG # убираем свой домен     sed -i "/^localhost/d" $TMP_LOG # убираем localhost     awk -F/ '{print $3}' $TMP_LOG > $TMP_AWK # оставляем только домен от url     cat $TMP_AWK > $TMP_LOG      # Разблокируем заблокированных referer     while read line         do             if [[ "$line" == *=* ]]; then                 GET_TIME=`echo $line | awk -F"=" '{print $2}'`                 NOW=`date +%s`                 #echo $NOW                 #echo $GET_TIME                 if [ "$NOW" -gt "$GET_TIME" ]; then                     REFERER=`echo $line | awk '{print $4}'`                     e "Referer $REFERER unblocked." >> $LOGFILE2                     /bin/sed -i '/'$REFERER'/d' $LOGFILE                     /bin/sed -i '/'$REFERER'/d' $REFCONF 		    NEED_NGINX_RELOAD=1                fi             fi         done < $LOGFILE      # Блокируем referer     while read line     do         REFERER=$line          DOUBLE=`cat $REFCONF | grep "$REFERER"`         if [ -n "$DOUBLE" ]; then             [ "$debug" != "0" ] && e "referer $REFERER exist in DROP rule" 	else 	    if [ "$BLOCK_ENABLE" = "true" -a "$LA_CURRENT" -gt "$LA" ];then 		echo "~$REFERER 1;" >> $REFCONF 		e "Referer $REFERER blocked $BLOCK_TIME minutes ($D) Unblock = `date --date="$BLOCK_TIME minute" +%s`" >> $LOGFILE 		e "Referer $REFERER blocked $BLOCK_TIME minutes ($D)" >> $LOGFILE2 	        NEED_NGINX_RELOAD=1 		if [ ! -s "$MSG_ALERT" ];then  		    echo "Status: WARNING" > $MSG_ALERT 		    echo "Date: $DT" >> $MSG_ALERT 		    echo "Referer: $RECORDS matches from 10000" >> $MSG_ALERT  		    echo "LA: $LA_CURRENT" >> $MSG_ALERT 		    echo "Referer(s) is blocked on $BLOCK_TIME minutes:" >> $MSG_ALERT 		    echo "" >> $MSG_ALERT 		fi 		msg 	    elif [ "$BLOCK_ENABLE" = "true" -a "$LA_CURRENT" -le "$LA" ];then     		TESTDOUBLE=`cat $LOGFILE | grep "$REFERER"` 	        if [ -z "$TESTDOUBLE" ]; then 		    e "Referer $REFERER TEST blocked $BLOCK_TIME minutes ($D) Unblock = `date --date="$BLOCK_TIME minute" +%s`" >> $LOGFILE 		    e "TEST. Referer $REFERER TEST blocked $BLOCK_TIME minutes ($D)" >> $LOGFILE2 		    if [ ! -s "$MSG_ALERT" ];then  			echo "Status: Notice" > $MSG_ALERT 			echo "Date: $DT" >> $MSG_ALERT 			echo "Referer: $RECORDS matches from 10000" >> $MSG_ALERT  			echo "LA: $LA_CURRENT" >> $MSG_ALERT 			echo "Referer(s) not blocking:" >> $MSG_ALERT 			echo "" >> $MSG_ALERT 		    fi 		    msg 		fi 	    else     		TESTDOUBLE=`cat $LOGFILE | grep "$REFERER"` 	        if [ -z "$TESTDOUBLE" ]; then 		    e "Referer $REFERER TEST blocked $BLOCK_TIME minutes ($D) Unblock = `date --date="$BLOCK_TIME minute" +%s`" >> $LOGFILE 		    e "TEST. Referer $REFERER TEST blocked $BLOCK_TIME minutes ($D)" >> $LOGFILE2 		    if [ ! -s "$MSG_ALERT" ];then  			echo "Date: $DT" > $MSG_ALERT 			echo "Current referer found over $RECORDS matches from 10000 records, but script working is TEST MODE " >> $MSG_ALERT  			echo "Current LA - $LA_CURRENT" >> $MSG_ALERT 			echo "Referer(s) not blocking:" >> $MSG_ALERT 			echo "" >> $MSG_ALERT 		    fi 		    msg 		fi 	    fi 	fi      done < $TMP_LOG  [ -n "email" -a -s "$MSG_ALERT" ] && send_mail  done  # reload nginx if config change if [ $NEED_NGINX_RELOAD -eq 1 ]; then   /sbin/service nginx reload >/dev/null 2>/dev/null fi  /bin/rm -rf $LOCK 

Конфигурационный файл nginx как симлинк указывает на один из двух файлов, работающих в обычном и high LA режимах.

Режим переключается скриптом, исполняемым ежеминутно из Cron

Скрипт переключения режимов:

#!/bin/bash  ### check LA level MAX_LA=10  processid=`/sbin/pidof -x $(basename $0) -o %PPID` if [[ $processid ]];then exit fi  CFG_DDOS='fpm.domain.ru.ddos' CFG_NODDOS='fpm.domain.ru.noddos'   load_average=$(uptime | awk '{print $11}' | cut -d "." -f 1) echo "$(date '+%Y-%m-%d %H:%M') : LA $load_average"  if [[ $load_average -ge $MAX_LA ]]; then   if [ -f /tmp/la_flag ]; then     date '+%s' > /tmp/la_flag      exit 1   else #    echo "$(date +%Y-%m-%d-%H-%M)"     date '+%s' > /tmp/la_flag      mv /etc/nginx/vhosts.d/new.domain.ru.conf /etc/nginx/vhosts.d/new.domain.ru.conf.bak > /dev/null 2>&1     ln -s /etc/nginx/vhosts.d/$CFG_DDOS /etc/nginx/vhosts.d/new.domain.ru.conf     reload=`/usr/sbin/nginx -t 2>&1 | grep ok`     if [ -n "$reload" ];then       /sbin/service nginx reload       rm -f /etc/nginx/vhosts.d/new.domain.ru.conf.bak > /dev/null 2>&1       echo "$(date '+%Y-%m-%d %H:%M') : DDOS config up $reload"       exit 0     else       /sbin/service nginx configtest 2>&1       mv /etc/nginx/vhosts.d/new.domain.ru.conf.bak /etc/nginx/vhosts.d/new.domain.ru.conf > /dev/null 2>&1       echo "nginx error config ddos test failed"       echo "alarm nginx config ddos test failed" | mail -s alarm root		       exit 1     fi   fi else   if [ -f /tmp/la_flag ]; then     TIMEA=`cat /tmp/la_flag`     TIMEC=`date '+%s'`     TIMED=$(( $TIMEC - $TIMEA ))     if [ $TIMED -gt 600 ]; then       echo "high LA ENDED $(date +%Y-%m-%d-%H-%M)"       rm -f /tmp/la_flag > /dev/null 2>&1       mv /etc/nginx/vhosts.d/new.domain.ru.conf /etc/nginx/vhosts.d/new.domain.ru.conf.bak > /dev/null 2>&1       ln -s /etc/nginx/vhosts.d/$CFG_NODDOS /etc/nginx/vhosts.d/new.domain.ru.conf       reload=`/usr/sbin/nginx -t 2>&1 | grep ok`       echo "$(date '+%Y-%m-%d %H:%M') : NO DDOS config up $reload"       if [ -n "$reload" ];then          /sbin/service nginx reload          rm -f /etc/nginx/vhosts.d/new.domain.ru.conf.bak > /dev/null 2>&1          echo "$(date '+%Y-%m-%d %H:%M') : NO ddos config up" 	 exit 0       else         /sbin/service nginx configtest 2>&1         mv /etc/nginx/vhosts.d/new.domain.ru.conf.bak /etc/nginx/vhosts.d/new.domain.ru.conf > /dev/null 2>&1 	echo "nginx error config noddos test failed" 	echo "alarm nginx config noddos test failed" | mail -s alarm root         exit 1       fi     else       exit 1     fi   else     exit 1   fi fi  

Часть конфигурации вынесена в отдельный файл, подключаемый в основных конфигурационных файлах.

Файл с общими параметрами конфигурации для обоих режимов vhosts.d/map.domain.ru.inc:

map_hash_bucket_size 128; geoip_country /usr/share/GeoIP/GeoIP.dat;  limit_req_zone $newlimit_addres1 zone=newone:10m rate=50r/m;  map $whitelist-$remote_addr:$remote_port $newlimit_addres1 {     ~"^0"                   $binary_remote_addr;     ~"^1-(?<match_rap>.*)"  $match_rap; }  geo $whitelist {    default 0;    91.205.47.150 1;    194.87.91.154 1;    83.69.225.78 1;    77.88.18.82 1;    91.143.46.202 1;    213.180.192.0/19 1;    87.250.224.0/19 1;    77.88.0.0/18 1;    93.158.128.0/18 1;    95.108.128.0/17 1;    178.154.128.0/17 1;    199.36.240.0/22 1;    84.201.128.0/18 1;    141.8.128.0/18 1;    188.134.88.105 1;    89.163.3.25 1;    46.39.246.91 1;    84.21.76.123 1;    136.243.83.53 1;    77.50.238.152 1;    83.167.117.49 1;    109.188.82.40 1;    79.141.227.19 1;    176.192.62.78 1;    86.62.91.133 1;    144.76.88.101 1; }  # блокировка рефереров через скрипт block_referer.sh map $http_referer $bad_referer {     default      "0";     include /etc/nginx/referer-block.conf; } map $http_referer:$request_method $bad_post_referer {     default      "0";     "~*domain.ru.*:POST$" "0";     "~*:POST$" "1";     include /etc/nginx/referer-block.conf; }  # Некоторый набор спицефичных блокировок проекта map $query_string $bad_query { ...     default 0; }  # проверка кук, которые устанавливают в файле /checkcapcha.php map $http_cookie $allowed_cookie {   "~somecookie" 1;   default  0; }  Ограничение по GeoIP в режиме Под атакой map $geoip_country_code $allowed_country {     RU 1;     default 0; }  # блокировка подсетей Amazon include vhosts.d/deny-amazon.inc;  # Ручные белый и черный списки map $remote_addr $valid_addr {     include vhosts.d/main_blacklist.map;     include vhosts.d/main_whitelist.map;     default 2; }  # UA посетителей-ботов map $http_user_agent $user_agent_search_bot {     "~Yandex"           "1";     "~Google"           "1";     "~*bing"            "1";     "~*MSNBot"          "1";     default             ""; }  map $remote_addr $ptr_wl_bl {     include vhosts.d/ptr_blacklist.map;     include vhosts.d/ptr_whitelist.map;     default ""; } map "$user_agent_search_bot:$ptr_wl_bl" $searchbot {     "1:1"  "1";     "1:0"  "0";     default  "2"; } 

Листинги конфигурационных файлов

Основной конфиг для нормального режима работы vhosts.d/fpm.domain.ru.noddos:

include vhosts.d/map.domain.ru.inc;  map "$searchbot:$valid_addr:$bad_referer:$bad_query" $root_location_p1 {     default   @allow_limit;     "~^1:"    @allow;     "~^2:1"   @allow_limit;      "~^0"      @loc_403;     "~^2:0"    @loc_403;     "2:2:1:0"  @loc_403;     "2:2:1:1"  @loc_403;     "2:2:0:1"  @loc_403; }  map "$searchbot:$valid_addr:$bad_post_referer:$bad_query" $root_only_location_p1 {     default   @allow_limit;     "~^1:"    @allow;     "~^2:1"   @allow_limit;      "~^0"      @loc_403;     "~^2:0"    @loc_403;     "2:2:1:0"  @loc_403;     "2:2:1:1"  @loc_403;     "2:2:0:1"  @loc_403; }  ########################################################  server {      listen 80;     listen 443 ssl;      fastcgi_read_timeout 300s;     fastcgi_send_timeout 300s;     fastcgi_connect_timeout 300s;      server_name domain.ru www.domain.ru m.domain.ru www.m.domain.ru;       ssl_certificate ssl/www.domain.ru.crt;     ssl_certificate_key ssl/www.domain.ru.key;     charset UTF-8;      access_log /srv/www/domain/shared/log/domain-acc.log main;     error_log /srv/www/domain/shared/log/domain-err.log;      root   /srv/www/domain/current/public/;      error_page 500 502 /highla.html;  # Выдается capcha в фарме POST с action="/checkcapcha.php"     location = /highla.html {         charset UTF-8;         root   /srv/www/domain/current/public/;         allow all;     }  # Устанавливается хэшированная кука на базе адреса посетителя.     location = /checkcapcha.php {         charset UTF-8;         root   /srv/www/domain/current/public/;         include fastcgi_params;         fastcgi_buffers 8 16k;         fastcgi_buffer_size 32k;         fastcgi_index index.php;         fastcgi_param  SCRIPT_FILENAME $realpath_root$fastcgi_script_name;         fastcgi_param  REQUEST_SCHEME     $scheme;         fastcgi_param  HTTPS              $https if_not_empty;         fastcgi_pass 127.0.0.1:9000;         allow all;     }  # Именованные location для ветвления по ним через переменную map     location @loc_403 {       access_log /srv/www/domain/shared/log/loc_403-acc main;       return 403;     }      location @allow {         access_log /srv/www/domain/shared/log/allow-acc main;         add_header X-debug-message "Allow";         try_files $uri /index.php?$query_string;     }      location @allow_limit {         limit_req zone=newone burst=15;         access_log /srv/www/domain/shared/log/allow-acc main;         add_header X-debug-message "Allow";         try_files $uri /index.php?$query_string;     }      location @deny {         access_log /srv/www/domain/shared/log/deny-acc main;         add_header X-debug-message "Deny";         return 403;     }     location @restrict {         access_log /srv/www/domain/shared/log/resrtict-acc main;         add_header X-debug-message "Restrict";         return 502;     }      location / {         try_files /fake-nonexistens-location-forr273 $root_location_p1;     }      location = / {         try_files /fake-nonexistens-location-forr273 $root_only_location_p1;     }      location ~* \.php {        include fastcgi_params;        fastcgi_buffers 8 16k;        fastcgi_buffer_size 32k;        fastcgi_index index.php;        fastcgi_param  SCRIPT_FILENAME $realpath_root$fastcgi_script_name;        fastcgi_param  REQUEST_SCHEME     $scheme;        fastcgi_param  HTTPS              $https if_not_empty;        fastcgi_pass 127.0.0.1:9000;     }      location ~* \.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|tar|mid|midi|wav|bmp|rtf|js|swf|flv|avi|djvu|mp3)$ {         root /srv/www/domain/current/public; 	expires 7d; 	access_log off; 	log_not_found off;     }      location ~ /\.git {         deny all;     }      location ~ /\.ht {         deny all;     }      location ~ /\.svn {         deny all;     }  } 

Конфиг для режима high LA vhosts.d/fpm.domain.ru.ddos:

include vhosts.d/map.domain.ru.inc;  map "$searchbot:$valid_addr:$bad_referer:$bad_query" $root_location {     default   @main;     "~^1:"    @allow;     "~^2:1"   @allow;      "~^0"      @loc_403;     "~^2:0"    @loc_403;     "2:2:1:0"  @loc_403;     "2:2:1:1"  @loc_403;     "2:2:0:1"  @loc_403; }  map "$searchbot:$valid_addr:$bad_post_referer:$bad_query" $root_only_location {     default   @main;     "~^1:"    @allow;     "~^2:1"   @allow;      "~^0"      @loc_403;     "~^2:0"    @loc_403;     "2:2:1:0"  @loc_403;     "2:2:1:1"  @loc_403;     "2:2:0:1"  @loc_403; }  map "$allowed_country:$allowed_cookie" $main_location {     "1:0"    @allow_limit;     "1:1"    @allow_limit;     "0:1"    @allow_limit;     default  @restrict; }  ########################################################  server {     listen 80;     listen 443 ssl;          fastcgi_read_timeout 300s;     fastcgi_send_timeout 300s;     fastcgi_connect_timeout 300s;      server_name domain.ru www.domain.ru m.domain.ru www.m.domain.ru;      ssl_certificate ssl/www.domain.ru.crt;     ssl_certificate_key ssl/www.domain.ru.key;     charset UTF-8;      access_log /srv/www/domain/shared/log/domain-acc.log main;     error_log /srv/www/domain/shared/log/domain-err.log;      root   /srv/www/domain/current/public/;  # Выдается capcha в фарме POST с action="/checkcapcha.php"     error_page 500 502 /highla.html;     location = /highla.html {         charset UTF-8; 	root   /srv/www/domain/current/public/;         allow all;     }  # Устанавливается хэшированная кука на базе адреса посетителя.     location = /checkcapcha.php {         charset UTF-8;         root   /srv/www/domain/current/public/;         include fastcgi_params;         fastcgi_buffers 8 16k;         fastcgi_buffer_size 32k;         fastcgi_index index.php;         fastcgi_param  SCRIPT_FILENAME $realpath_root$fastcgi_script_name;         fastcgi_param  REQUEST_SCHEME     $scheme;         fastcgi_param  HTTPS              $https if_not_empty;         fastcgi_pass 127.0.0.1:9000;         allow all;     }  # Именованные location для ветвления по ним через переменную map     location @loc_403 {       access_log /srv/www/domain/shared/log/loc_403-acc main;       return 403;     }          location @allow {         access_log /srv/www/domain/shared/log/allow-acc main;         add_header X-debug-message "Allow";         try_files $uri /index.php?$query_string;     }      location @allow_limit {         limit_req zone=newone burst=55;         access_log /srv/www/domain/shared/log/allow-limit-acc main;         add_header X-debug-message "Allow";         try_files $uri /index.php?$query_string;     }      location @deny {         access_log /srv/www/domain/shared/log/deny-acc main;         add_header X-debug-message "Deny";         return 403;     }     location @restrict {         access_log /srv/www/domain/shared/log/resrtict-acc main;         add_header X-debug-message "Restrict";         return 502;     }      location @main {         add_header X-debug-message "Main"; 	try_files /fake-nonexistens-location-forr273 $main_location;     }      location / { 	try_files /fake-nonexistens-location-forr273 $root_location;     }      location = / { 	try_files /fake-nonexistens-location-forr273 $root_only_location;     }      location ~* \.php {        include fastcgi_params;        fastcgi_buffers 8 16k;        fastcgi_buffer_size 32k;        fastcgi_index index.php;        fastcgi_param  SCRIPT_FILENAME $realpath_root$fastcgi_script_name;        fastcgi_param  REQUEST_SCHEME     $scheme;        fastcgi_param  HTTPS              $https if_not_empty;        fastcgi_pass 127.0.0.1:9000;     }      location ~* \.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|tar|mid|midi|wav|bmp|rtf|js|swf|flv|avi|djvu|mp3)$ {         root /srv/www/domain/current/public;         expires 7d;         access_log off;         log_not_found off;     }      location ~ /\.git {         deny all;     }      location ~ /\.ht {         deny all;     }      location ~ /\.svn {         deny all;     } } 

Итог

Этим решением мы помогли нашему клиенту защитить свой проект от паразитного трафика и повысить стабильность работы серверов.
Автор: ведущий системный администратор компании Марат Рахимов.

ссылка на оригинал статьи https://habrahabr.ru/post/278553/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *