LINUX.ORG.RU
ФорумAdmin

Блокировка частых запросов с одного IP средствами nginx

 , , ,


0

2

Делаю блокировку примерно как в пункте 5 вот этой статьи:

https://cloud.vk.com/blog/zaschita-ot-ddos-atak-sredstvami-nginx

Заодно читаю официальную документацию:

https://cloud.vk.com/blog/zaschita-ot-ddos-atak-sredstvami-nginx

И в моем случае конфигурирующие опции прописаны такие:

# Файл /etc/nginx/sites-available/mysite

limit_req_zone $binary_remote_addr zone=ipAddrZone:10m rate=5r/m;
...
server {
    ...
    location /punbb/register.php {
        limit_req zone=ipAddrZone;
    }

Nginx перезагружен.

И ничего эти настройки не блокируют. Я захожу на страницу /punbb/register.php, обновляю ее хоть каждую секунду по 20 раз непрерывно, ответ от сервера всегда нормально приходит.

Вопрос 1: Почему не работает блокировка?

Вопрос 2: Что должно происходить с запросом, когда он по заданным условиям превысил лимит? Он по-умолчанию просто не обрабатывается и выбрасывается? На него выдается ответ с кодом ошибки 503? Он ставится в очередь и будет обработан по истечении периода проверки? Что конкретно?

★★★★★
Ответ на: комментарий от vel

Я читал и не понимаю. Там написано, что если сделать nodelay то пакеты не будут задерживаться.

И что это значит? Что пакеты без nodelay будут пропускаться? Или что они должны отсеиваться? Или что?

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

В смысле пакеты? Запросы.

https://blog.nginx.org/blog/rate-limiting-nginx

без nodelay оно продолжает обрабатывать очередь запросов, но с задержкой.

С nodelay лишние запросы сразу завершаются ошибкой 503 (429?)

vel ★★★★★
()
Последнее исправление: vel (всего исправлений: 1)

УМВР

Скорее всего у тебя проблема не в limit_req а в том что урл обрабатывается не в этом location.

Если бы он обрабатывался в этом - тебе бы отвечалось исходным кодом пхп-скрипта т.к. инструкций для отправки его в php-fpm я рядом не вижу.

firkax ★★★★★
()
Ответ на: комментарий от firkax

У меня location в пределах сервера перечислены вот так:

    location / {
        # First attempt to serve request as file, then
        # as directory, then fall back to displaying a 404.
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php5.6-fpm.sock;
    }

    # Правила для блокировки спама на форуме - блокируются частые попытки регистрации
    location /punbb/register\.php {
        limit_req zone=ipAddrZone;
    }
    location /punbb/extensions/pun_antispam/image\.php.* {
        limit_req zone=ipAddrZone;
    }

Может быть, надо как-то вложенно блокирующие локации перечислять?
Писать внутри «location ~ \.php$»? Вот здесь написано про вложенность, но никаких примеров нет: https://itzx.ru/linux/derektiva-nginx-location-s-primerami

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от vel

Я проверил варианты со строкой:

limit_req zone=ipAddrZone nodelay;

Никакой блокировки не добился. Но nodelay оставил, видимо эта опция нужна.

Походу надо что-то сделать с location.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea
location /punbb/register\.php

Гм. тебя ничего не смущает в этом location?

IMHO обратный слеш тут лишний.

Ну и следуюший location туда же.

IMHO nginx выбирает один location и использует его.

Я всегда делаю именнованый location @php для обработки .php, а из нормальных location требующих обработки php делаю try_files . @php

vel ★★★★★
()
Последнее исправление: vel (всего исправлений: 1)
Ответ на: комментарий от firkax

Я пробовал разместить следующие локации:

    location /punbb/register.php {
        limit_req zone=ipAddrZone nodelay;
    }
    location /punbb/extensions/pun_antispam/image.php {
        limit_req zone=ipAddrZone nodelay;
    }
перед локацией location ~ \.php$ { в конфиге. Толку никакого.

Пробовал перенести эти локации внутрь локации location ~ \.php$ { (т. е. вложенно), но тогда nginx не стартует и в логе ошибка:
location "/punbb/register.php" is outside location "\.php$"

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от vel

Гм. тебя ничего не смущает в этом location?
IMHO обратный слеш тут лишний.

Но ведь в доке написано, что это регулярка. Если на то пошло, правильно надо писать:

location \/punbb\/register\.php

Разве нет?

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от vel

То есть, задача нерешаемая?

Если проверять локацию на частые запросы, то ее нельзя обработать через php-fpm?

Кроме того, локация location /punbb/register.php не обрабатывает ссылки типа /punbb/register.php?action=register?

Xintrea ★★★★★
() автор топика
Последнее исправление: Xintrea (всего исправлений: 1)
Ответ на: комментарий от Xintrea

ты слепой?

Прочти про именованную location и try_files

 location @php {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php5.6-fpm.sock;
 }
 location ~ \.php$ {
        try_files . @php;
 }

 location /punbb/register.php {
        limit_req zone=ipAddrZone;
        try_files . @php;
 }

Кроме того, локация location /punbb/register.php не обрабатывает ссылки типа /punbb/register.php?action=register?

Почему?

vel ★★★★★
()
Последнее исправление: vel (всего исправлений: 1)
Ответ на: комментарий от Xintrea

Ты неправильно понимаешь что такое location. Это не условные блоки настроек, применяющиеся в зависимости от соответствия их урлу, это ветвление. если ты попал в одну из локаций, то ты остаёшься в ней, и остальные уже не применяются (если ты только явно не укажешь перейти в другую в конкретном месте). Локации с регулярками имеют более высокий приоритет чем дефолтные, т.е. у тебя всё попадает в ~ \.php$.

Чтобы сделать локацию с приоритетом выше регулярок, сделай её ^~ /path/to/file.php и продублируй в неё fastcgi_pass и остальное.

Экранировать точку в локации-нерегулярке не надо.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от vel

Ну вот я сделал такой конфиг и перегрузил сервис nginx:

# Выделение зоны в памяти размером 10Мб для накопления IP-адресов запросов
# чтобы их ограничить для заданных URL-ов 5 штуками в минуту
limit_req_zone $binary_remote_addr zone=ipAddrZone:10m rate=5r/m;

server {
...

    # Именованная локация, содержимое которой не обрабатывается напрямую,
    # а вызывается из других локаций
    location @php {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php5.6-fpm.sock;
     }

    location / {
        # First attempt to serve request as file, then
        # as directory, then fall back to displaying a 404.
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        try_files . @php; # Вызов именованной локации
    }


    # Правила для блокировка спама на форуме - блокируются частые попытки регистрации
    # с одного и того же IP-адреса
    location /punbb/register.php {
        limit_req zone=ipAddrZone nodelay;
        try_files . @php; # Вызов именованной локации
    }

    location /punbb/extensions/pun_antispam/image.php {
        limit_req zone=ipAddrZone nodelay;
        try_files . @php; # Вызов именованной локации
    }
}

Никакого толку, хоть сто запросов в минуту делай, ничего не блокируются. Вот сами запросы, если что:

195.182.157.224 - - [22/May/2024:18:14:21 +0300] 
"GET /punbb/register.php HTTP/1.1" 200 4460 
"https://имя_сайта/punbb/" "Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0"

195.182.157.224 - - [22/May/2024:18:14:23 +0300]
"GET /punbb/extensions/pun_antispam/image.php?36cec89d1cd70c29b781a1814dfb1ba6 HTTP/1.1" 200 6839
"https://имя_сайта/punbb/register.php" "Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0"

Вопрос: почему такой вариант конфига не работает как надо?

Xintrea ★★★★★
() автор топика
Последнее исправление: Xintrea (всего исправлений: 2)
Ответ на: комментарий от firkax

Ага, спасибо. Добавил ^~ в этот конфиг: Блокировка частых запросов с одного IP средствами nginx (комментарий)

Вместо:

location /punbb/register.php {
...
location /punbb/extensions/pun_antispam/image.php {

Стало:
location ^~ /punbb/register.php {
...
location ^~ /punbb/extensions/pun_antispam/image.php {

И все заработало.

Только чтобы адекватнее срабатывало, сделал в limit_req еще параметр burst=3.

Xintrea ★★★★★
() автор топика
Последнее исправление: Xintrea (всего исправлений: 2)
Ответ на: комментарий от vel

Блин, нет, не все заработало.

Теперь проблема в локации, у которой нет *.php в пути. Это не punbb-форум, это другая часть сайта. Она тоже через PHP работает, и вроде как никаких дополнительных настроек раньше не требовала.

Но почему-то такая локация не блокируется, сколько ты запросов не делай:

    # Это условие работает
    location ^~ /punbb/register.php {
        limit_req zone=ipAddrZone burst=4 nodelay;
        try_files . @php; # Вызов именованной локации
    }

    # Это условие не работает
    location ^~ /site/page/index/guestbook {
        limit_req zone=ipAddrZone burst=4 nodelay;
        try_files . @php; # Вызов именованной локации
    }

Почему так?

Запрос, который надо фильтровать такой:
195.182.157.224 - - [22/May/2024:19:32:32 +0300]
"GET /site/page/index/guestbook HTTP/1.1" 200 9438
"https://имя_сайта/" "Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0"


(Так же кастуется firkax)

Xintrea ★★★★★
() автор топика
Последнее исправление: Xintrea (всего исправлений: 2)
Ответ на: комментарий от Xintrea

Убери костыль с try_files, я тебе написал что надо сделать. Ещё раз: кроме основной локации делай вторую, в которую попадает изранная часть её урлов, полностью копируй в ней конфиг основной + добавляй лимит. Никакой магии делать не пытайся, у тебя теперь просто две локации - одна для лимитированных запросов, вторая для всех остальных.

firkax ★★★★★
()
Ответ на: комментарий от firkax

Ты писал:

Локации с регулярками имеют более высокий приоритет чем дефолтные, т.е. у тебя всё попадает в ~ \.php$.


А сейчас речь идёт о локации без постфикса php, поэтому она попасть в «~ \.php$» не может.

Кроме того, локация «location ^~ /punbb/register.php» отрабатывает отдельно, потому что блокировка работает именно на этой локации, а не на всем сайте и не на всем форуме. Т.е она не попадает под «~ \.php$».

Поэтому непонятно, с какого перепуга локация «location ^~ /site/page/index/guestbook» не обрабатывается блокировщиком.

Xintrea ★★★★★
() автор топика
Последнее исправление: Xintrea (всего исправлений: 2)
Ответ на: комментарий от Xintrea

А сейчас речь идёт о локации без постфикса php, поэтому она попасть в «~ \.php$» не может.

Конечно. Но куда-то она же попадает? Вот найди куда, склонируй именно ту локацию и добавь в неё лимит. Можешь ещё логи добавить в обе (и с лимитом и без) чтоб смотреть куда попал запрос.

firkax ★★★★★
()
Ответ на: комментарий от firkax

Конечно. Но куда-то она же попадает?

Она должна попасть в

location ^~ /site/page/index/guestbook

Потому что всеми символами попадает. Но этого не происходит, и я хочу понять почему.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от firkax

Был такой:

server {
    ...
    location / {
        # First attempt to serve request as file, then
        # as directory, then fall back to displaying a 404.
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php5.6-fpm.sock;
    }
}


Стал такой:
limit_req_zone $binary_remote_addr zone=ipAddrZone:10m rate=5r/m;

server {
    ...

    # Именованная локация, содержимое которой не обрабатывается напрямую,
    # а вызывается из других локаций
    # Описание: pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    location @php {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php5.6-fpm.sock;
     }

    location / {
        # First attempt to serve request as file, then
        # as directory, then fall back to displaying a 404.
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        try_files . @php; # Вызов именованной локации
    }


    # Правила для блокировка спама на форуме - блокируются частые попытки регистрации
    # с одного и того же IP-адреса
    
    location ^~ /punbb/register.php {
        limit_req zone=ipAddrZone burst=4 nodelay;
        try_files . @php; # Вызов именованной локации
    }

    location ^~ /punbb/extensions/pun_antispam/image.php {
        limit_req zone=ipAddrZone burst=4 nodelay;
        try_files . @php; # Вызов именованной локации
    }

    location ^~ /punbb/login.php {
        limit_req zone=ipAddrZone burst=4 nodelay;
        try_files . @php; # Вызов именованной локации
    }

    location ^~ /site/page/index/guestbook {
        limit_req zone=ipAddrZone burst=4 nodelay;
        try_files . @php; # Вызов именованной локации
    }
}


Все страницы работают.

На страницах /punbb/register.php, /punbb/extensions/pun_antispam/image.php, /punbb/login.php плюсом работает блокировка по IP при частом обновленни.

На странице /site/page/index/guestbook блокировка при частом обновлении не страбатывает.

Я не могу понять почeму.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

Ну так включи протоколирование ошибок на уровень debug в самом начале nginx.conf (если nginx собран с такой возможностью) и посмотри что происходит при обращении к /site/page/index/guestbook

vel ★★★★★
()
Ответ на: комментарий от Xintrea

На странице /site/page/index/guestbook блокировка при частом обновлении не страбатывает.

Не вижу этой страницы в старом конфиге. Получается, она бы там обрабатывалась в location / в режиме показа индекса соответствующей директории.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 2)
Ответ на: комментарий от firkax

Получается, она бы там обрабатывалась в location / в режиме показа индекса соответствующей директории.

А, я частично понял. Я то думал что все можно разрулить только фильтрацией локаций, и только их показывал. Перед локациями у меня прописано вот что:

    index index.php index.html index.htm;

    # Редирект с контроллера главной страницы на стартовый URL
    rewrite ^(.*)/site/page/index/main$ http://имя_сайта.ru permanent;

    if (!-e $request_filename) {
        rewrite "^/(.*)$" /index.php?$1 last;
    }

То есть, все что запрашивается по ссылке без php, определяется как обращение к директории/файлу. И это обращение реврайтится на использование /index.php, а сам линк передается как параметр GET-запроса. Это рекомендованная настройка фреймверка, который у меня используется.

Я включил отладку, и увидел, как преобразуется строка запроса https://имя_сайта/site/page/index/guestbook. Получается вот так:
18:15:22 [debug] 101227#101227:
*5 http upstream request: "/index.php?site/page/index/guestbook"

Увидев это, я подправил локацию на такую:
    location ^~ /index.php?site/page/index/guestbook {
        limit_req zone=ipAddrZone burst=4 nodelay;
        try_files . @php; # Вызов именованной локации
    }

Перегрузил nginx. Но и такая настройка не блокирует частые обращения к этой странице.

Тогда я попробовал проверить, что будет если я в строке браузера напишу напрямую эту же локацию:
https://имя_сайта/index.php?site/page/index/guestbook

Я то думал, что увижу страницу гостевой, но нет - меня перебросило на главную страницу сайта. Удивительно.

Тогда я написал в строке браузера то же самое, только поставив косую «/» после знака "?", вот так:
https://имя_сайта/index.php?/site/page/index/guestbook

И у меня отобразилась гостевая книга. Но частые запросы на этот «прямой» URL все так же не блокируются.

* * *

Теперь у меня помимо вопроса почему не работает блокировка частых обращений к странице, появился другой вопрос:

Почему URL /index.php?site/page/index/guestbook, получаемый в глубинах Nginx, отображает гостевую страницу, но этот же URL, будучи написанный напрямую в строке браузера - выкидывает на главную?

Xintrea ★★★★★
() автор топика
Последнее исправление: Xintrea (всего исправлений: 5)
Ответ на: комментарий от Xintrea

В location не учитываются аргументы запроса, то есть он там просто /index.php.

Вставить лимит можно несколькими способами. Рекомендую описанный ниже.

Реврайты вне локаций это плохо, т.к. они действуют на всё сразу и к ним сложно что-то прицепить. Поэтому реврайты (их же всего два такие?) переставляешь в location / - он там будет работать (вроде) не хуже, и так же после обработки будет искать другую локацию с .php. Возможно, потребуется вставить реврайт ещё в какую-то локацию (а не знаю полный их список и логику работы твоего движка), но это уже выяснится по ходу дела и скорее всего всё-таки нет.

Создаёшь вторую локацию (с guestbook по строгому соответствию), в неё уже вставляешь лимит и тот же реврайт.

Останется проблема - кто-то может начать обращаться к нему по урлу index.php?site/page/index/guestbook на котором лимита нет. Чтобы это не происходило, склонируй локацию .php в локацию /index.php по строгому соответствию и допиши в неё директиву internal; - это значит что обратиться к index.php напрямую нельзя, он только для внутреннего пользования реврайтами.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)