Правило маршрутизации / 01

Кто выиграл
location?

Если несколько блоков подходят к URI, порядок выбора важнее того, как правила выглядят в файле.

TRACE RULE

Сначала exact match, затем самый длинный prefix. Если у prefix нет ^~, NGINX проверяет regex по порядку и использует первое совпадение.

Документация по location ↗

Ситуация

Запрет есть.
Но он не выбран.

Префиксный блок закрывает каталог, а regex-блок передаёт PHP-запрос приложению. Запрос /private/export.php совпадает с обоими правилами. Если prefix не отмечен как ^~, совпавший regex может стать итоговым location.

NGINX CONFIGДва подходящих блока
location /private/ {
    deny all;
}

location ~ \.php$ {
    proxy_pass http://app:9000;
}
ИЗОЛИРОВАННЫЙ ПРОГОН / NGINX 1.30.6

Что меняет ^~

БЕЗ ^~
Побеждает regex

Получен HTTP 200; mock upstream принял /private/export.php.

200 / upstream hit
С ^~
Побеждает prefix

Получен HTTP 403; второе обращение не появилось в журнале mock upstream.

403 / no upstream

Проверено на loopback с NGINX 1.30.6 и mock upstream вместо app:9000. Это воспроизводимый локальный тест, не production-сервис и не замена проверке прав в backend.

КЛЮЧ Читай приоритет, а не только порядок строк. Для regex важен порядок самих regex-блоков.

Интерактивная трасса / модель

Запусти запрос.
Посмотри, кто победил.

Меняй сценарий, Host и URI. Линия показывает путь, а результат объясняет выбранное правило.

Это ограниченные учебные примеры, а не полноценный парсер nginx.conf. Перед исправлением сверяйся с документацией и тестируй конфигурацию.

Выбери точку сбоя

Трасса запросаREADY
  1. listenадрес и порт
  2. serverвиртуальный хост
  3. locationвыбранный URI
  4. handlerфайл или proxy
  5. appпроверка прав

Развилка / location

один URI · два кандидата
PREFIXlocation /private/совпал, но уступилdeny all
REGEXlocation ~ \\.php$выбранный блокapp:9000
Входapi.demo.test/private/export.php
Выбранный маршрутregex → http://app:9000

Обычный prefix совпал, но регулярное выражение для .php тоже совпадает. Без ^~ NGINX проверяет regex после выбора самого длинного prefix. В этой конфигурации regex location может стать победителем, поэтому deny all из prefix-блока не применяется.

Что проверить

Если весь /private/ должен оставаться закрытым, сделайте prefix ^~ и проверьте точный набор правил: location ^~ /private/ { deny all; }

Исправление

Закрепи
границу.

Если весь путь /private/ должен оставаться закрытым, ^~ говорит NGINX не проверять regex после выбора этого prefix.

NGINX CONFIGЯвно защищённый prefix
location ^~ /private/ {
    deny all;
}

location ~ \.php$ {
    proxy_pass http://app:9000;
}
ПРОВЕРЬ ПОСЛЕ ИЗМЕНЕНИЯnginx -tcurl -i -H "Host: api.demo.test" http://127.0.0.1:8080/private/export.php

В примере порт 8080. Подставь свой listen, затем проверь статус, тело ответа и журнал приложения.

Граница ответственности

Даже правильно закрытый NGINX-маршрут не заменяет проверку пользователя и прав в backend. Защищённый объект должен проверяться на каждом действии приложения.

OWASP Authorization Cheat Sheet ↗
ПУБЛИЧНЫЙ SECURITY ADVISORY21 OCT 2021 / CVE-2021-25742

Snippets
пересекли границу.

В историческом advisory Kubernetes Security Response Committee описал риск ingress-nginx: пользователь с правом создавать или менять Ingress-объекты мог злоупотребить custom snippets и получить Kubernetes Secrets из разных namespace в затронутой конфигурации.

ВОЗДЕЙСТВИЕ

Доступ к секретам кластера при недостаточном разделении прав в multi-tenant среде.

КОНТРОЛЬ

Отключить snippets для недоверенных авторов, ограничить RBAC и обновляться по актуальным advisory и поддерживаемым выпускам.

Это риск контроллера ingress-nginx и его annotations, а не ошибка выбора обычного location в standalone NGINX.

Официальный advisory Kubernetes ↗