← К результатам

nxray Security Wiki

Справочник по проверкам безопасности Nginx. Для каждой проверки — уязвимый код, правильный код, kill chain и рекомендации.

Оглавление

Критичные проверки

HIGH
HIGH

SSRF через proxy_passПодстановка пользовательских данных в upstream-адрес

Почему опасно

Если в proxy_pass используется переменная из пользовательского ввода ($arg_*, $http_*), атакующий может заставить nginx отправить запрос на произвольный внутренний сервис. Это позволяет сканировать внутреннюю сеть, обращаться к metadata-сервисам облачных провайдеров (169.254.169.254), красть credentials.

Kill Chain

1. Атакующий отправляет запрос: GET /api/?target=169.254.169.254/latest/meta-data/
2. Nginx подставляет значение в proxy_pass и отправляет запрос на metadata-сервис
3. Ответ с AWS/GCP credentials возвращается атакующему
4. Полный доступ к облачной инфраструктуре

Код

Уязвимо
location /api/ {
    proxy_pass http://$arg_target;
}
Правильно
map $arg_target $backend {
    default         backend;
    "service-a"     service-a;
    "service-b"     service-b;
}

location /api/ {
    proxy_pass http://$backend;
}
HIGH

HTTP Response SplittingИнъекция заголовков через пользовательские переменные

Почему опасно

Переменные $request_uri, $arg_*, $http_* содержат сырой пользовательский ввод. При подстановке в заголовки или редиректы атакующий может внедрить символы \r\n и добавить произвольные HTTP-заголовки, включая Set-Cookie для угона сессий.

Kill Chain

1. Атакующий отправляет: GET /redirect%0d%0aSet-Cookie:%20admin=1 HTTP/1.1
2. Nginx подставляет $request_uri в Location-заголовок
3. В ответе появляется поддельный Set-Cookie заголовок
4. Жертва получает вредоносную cookie → session fixation / XSS

Код

Уязвимо
location /redirect {
    return 302 https://example.com$request_uri;
}

add_header X-Custom $http_referer;
Правильно
# $uri — уже нормализован nginx
location /redirect {
    return 302 https://example.com$uri;
}

# Не используйте $http_* в заголовках
HIGH

Подделка Host-заголовка$http_host вместо $host в proxy_set_header

Почему опасно

$http_host — это сырой заголовок Host из запроса клиента. Атакующий может подставить произвольный Host, что ведёт к cache poisoning, password reset poisoning (ссылка для сброса пароля указывает на домен атакующего), обходу виртуальных хостов на бэкенде.

Kill Chain

1. Атакующий отправляет: Host: evil.com
2. Бэкенд генерирует password reset ссылку с https://evil.com/reset?token=...
3. Жертва получает email и кликает по ссылке → токен утекает к атакующему

Код

Уязвимо
proxy_set_header Host $http_host;
Правильно
# $host — нормализован nginx,
# соответствует server_name
proxy_set_header Host $host;
HIGH

Path Traversal через aliasНесовпадение trailing slash между location и alias

Почему опасно

Если location /files (без /) указывает на alias /var/www/uploads (без /), то запрос /files../etc/passwd преобразуется в /var/www/uploads/../etc/passwd = /etc/passwd. Атакующий читает произвольные файлы на сервере.

Kill Chain

1. GET /files../etc/shadow
2. Nginx подставляет: alias + "../etc/shadow" = /var/www/uploads/../etc/shadow
3. Чтение /etc/shadow, конфигов БД, приватных ключей

Код

Уязвимо
location /files {
    alias /var/www/uploads;
}
Правильно
# Trailing slash и в location, и в alias
location /files/ {
    alias /var/www/uploads/;
}
HIGH

Переменная в root/aliasPath Traversal через подстановку переменной

Почему опасно

Если root или alias содержит переменную ($host, $uri и др.), атакующий может манипулировать путём для чтения произвольных файлов через path traversal (../).

Код

Уязвимо
location /dynamic {
    root /var/www/$host;
}
Правильно
# Фиксированный путь
location /dynamic {
    root /var/www/mysite;
}
HIGH

Устаревшие SSL-протоколыTLSv1 и TLSv1.1 уязвимы к POODLE, BEAST

Почему опасно

TLSv1.0 и TLSv1.1 имеют известные уязвимости (POODLE, BEAST, Lucky13). Атакующий в позиции MitM может дешифровать трафик. С 2020 года эти протоколы запрещены стандартами PCI DSS и RFC 8996.

Код

Уязвимо
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
Правильно
ssl_protocols TLSv1.2 TLSv1.3;
HIGH

Слабые SSL-шифрыDES, RC4, EXPORT — криптографически сломаны

Почему опасно

Шифры DES, RC4, EXPORT, NULL давно взломаны. Атаки SWEET32 (3DES), Bar Mitzvah (RC4) позволяют расшифровать трафик за разумное время. Не обеспечивают Forward Secrecy — компрометация ключа раскрывает весь прошлый трафик.

Код

Уязвимо
ssl_ciphers DES-CBC3-SHA:RC4-SHA:AES128-SHA;
Правильно
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers on;
HIGH

Listen 443 без sslПорт 443 принимает нешифрованный трафик

Почему опасно

Без флага ssl nginx принимает на порту 443 обычный HTTP. Клиенты ожидают TLS на 443 — получается либо ошибка соединения, либо (хуже) передача данных в открытом виде по каналу, который считается защищённым.

Код

Уязвимо
listen 443;
Правильно
listen 443 ssl http2;
HIGH

Отсутствие ssl_dhparamСлабый обмен ключами Диффи-Хеллмана

Почему опасно

Без явных DH-параметров nginx использует дефолтные 1024-битные, уязвимые к атаке Logjam. Атакующий в MitM-позиции может понизить обмен ключами и расшифровать трафик.

Код

Уязвимо
server {
    listen 443 ssl;
    # Нет ssl_dhparam
}
Правильно
# Генерация: openssl dhparam -out /etc/ssl/dhparam.pem 4096
ssl_dhparam /etc/ssl/dhparam.pem;
HIGH

Доступ к .gitУтечка исходного кода и секретов через /.git/

Почему опасно

Директория .git содержит полную историю репозитория: исходный код, пароли в коммитах, приватные ключи, конфигурации БД. Инструменты вроде git-dumper автоматически восстанавливают репозиторий по HTTP.

Kill Chain

1. GET /.git/HEAD → подтверждение наличия репозитория
2. git-dumper https://target.com/.git/ repo/
3. git log --all -p | grep -i password → утечка секретов из истории

Код

Уязвимо
server {
    root /var/www/html;
    # .git доступен по HTTP
}
Правильно
location ~ /\.git {
    deny all;
    return 404;
}
HIGH

Открытые dotfiles/.env, /.htpasswd, /.ssh/ доступны по HTTP

Почему опасно

.env содержит секреты приложения (DB_PASSWORD, API_KEY). .htpasswd — хэши паролей. .ssh/ — приватные SSH-ключи. Всё это доступно одним GET-запросом.

Код

Уязвимо
server {
    root /var/www/html;
    # нет блокировки dotfiles
}
Правильно
# Блокируем все dotfiles кроме .well-known (ACME)
location ~ /\.(?!well-known) {
    deny all;
}
HIGH

Открытые backup-файлы.bak, .sql, .tar.gz доступны для скачивания

Почему опасно

Файлы db.sql, backup.tar.gz, config.bak часто содержат полные дампы БД, конфигурации с паролями. Автоматические сканеры (nuclei, dirsearch) проверяют эти расширения в первую очередь.

Код

Уязвимо
server {
    root /var/www/html;
    # db_dump.sql доступен
}
Правильно
location ~* \.(bak|sql|tar|gz|zip|swp|old|orig|dump)$ {
    deny all;
}
HIGH

allow без deny allБелый список IP без закрывающего правила

Почему опасно

Без deny all; после allow nginx разрешает доступ ВСЕМ. Директивы allow/deny работают по принципу first match — без завершающего deny all белый список бесполезен.

Код

Уязвимо
location /internal {
    allow 10.0.0.0/8;
    # нет deny all — доступ для всех!
}
Правильно
location /internal {
    allow 10.0.0.0/8;
    deny all;
}
HIGH

Обход allow/deny через returnreturn выполняется ДО проверки access-модуля

Почему опасно

В nginx директива return обрабатывается на фазе rewrite, которая выполняется раньше фазы access (allow/deny). Поэтому return 200 в том же location что и deny all делает deny бесполезным — ответ отдаётся до проверки IP.

Код

Уязвимо
location /admin {
    allow 10.0.0.0/8;
    deny all;
    return 200 "admin";  # deny не работает!
}
Правильно
location /admin {
    allow 10.0.0.0/8;
    deny all;
    proxy_pass http://admin_backend;
}
# Или используйте if + return в отдельном блоке
HIGH

Admin-панель без аутентификации/admin, /phpmyadmin, /dashboard открыты всем

Почему опасно

Административные интерфейсы без auth_basic или IP-ограничений доступны из интернета. Боты постоянно сканируют стандартные пути (/admin, /wp-admin, /phpmyadmin). Даже если приложение имеет свою авторизацию — nginx-уровень защиты даёт defense in depth.

Код

Уязвимо
location /admin {
    proxy_pass http://backend;
}
Правильно
location /admin {
    auth_basic "Admin Area";
    auth_basic_user_file /etc/nginx/.htpasswd;
    allow 10.0.0.0/8;
    deny all;
    proxy_pass http://backend;
}
HIGH

set_real_ip_from 0.0.0.0/0Любой клиент может подделать свой IP

Почему опасно

Директива set_real_ip_from 0.0.0.0/0 доверяет заголовку X-Forwarded-For от ЛЮБОГО источника. Атакующий подставляет в X-Forwarded-For любой IP, обходя allow/deny правила, rate limiting и geo-блокировки.

Kill Chain

1. curl -H "X-Forwarded-For: 10.0.0.1" https://target.com/internal
2. Nginx считает что запрос от 10.0.0.1 (внутренняя сеть)
3. Обход allow 10.0.0.0/8; deny all; → доступ к внутренним ресурсам

Код

Уязвимо
set_real_ip_from 0.0.0.0/0;
real_ip_header X-Forwarded-For;
Правильно
# Только IP вашего балансировщика/CDN
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
HIGH

Внешний DNS resolverDNS poisoning и утечка запросов

Почему опасно

Использование внешних DNS (8.8.8.8) для резолвинга upstream в nginx создаёт точку для DNS poisoning — атакующий может подменить ответ и перенаправить трафик на свой сервер. Также DNS-запросы утекают наружу, раскрывая внутреннюю инфраструктуру.

Код

Уязвимо
resolver 8.8.8.8;
Правильно
resolver 127.0.0.1 valid=30s;
# Или корпоративный DNS-сервер
resolver 10.0.0.53;
HIGH

valid_referers noneЗащита от хотлинкинга обходится отсутствием Referer

Почему опасно

Параметр none в valid_referers разрешает запросы БЕЗ заголовка Referer. Любой инструмент (curl, скрипт) по умолчанию не отправляет Referer, полностью обходя защиту от хотлинкинга.

Код

Уязвимо
valid_referers none server_names;
if ($invalid_referer) {
    return 403;
}
Правильно
valid_referers server_names
    *.example.com;
if ($invalid_referer) {
    return 403;
}
HIGH

PHP FastCGI без try_filesFile upload → RCE через подделку .php пути

Почему опасно

Без try_files $uri =404; nginx передаёт в PHP-FPM запросы к несуществующим .php файлам. Атакующий загружает картинку с PHP-кодом (shell.jpg), затем обращается к /uploads/shell.jpg/x.php — PHP-FPM исполняет код из картинки.

Kill Chain

1. Загрузка картинки с PHP-кодом: shell.jpg содержит <?php system($_GET['c']); ?>
2. GET /uploads/shell.jpg/.php
3. PHP-FPM выполняет код из shell.jpg → RCE на сервере

Код

Уязвимо
location ~ \.php$ {
    fastcgi_pass unix:/run/php/php-fpm.sock;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
}
Правильно
location ~ \.php$ {
    try_files $uri =404;  # ← обязательно!
    fastcgi_pass unix:/run/php/php-fpm.sock;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
}
HIGH

Циклические rewriteБесконечный цикл перезаписи → 500 Internal Server Error

Почему опасно

Rewrite-правило, где выходной URL снова совпадает с входным паттерном, создаёт бесконечный цикл. Nginx прервёт его после 10 итераций с ошибкой 500. Это может быть использовано для DoS — одним запросом нагрузить CPU сервера.

Код

Уязвимо
rewrite ^/loop/(.*)$ /loop/$1 last;
Правильно
# Перезапись не должна совпадать с входом
rewrite ^/old/(.*)$ /new/$1 last;

Средние проверки

MEDIUM
MEDIUM

Раскрытие внутренних IPBackend отдаёт Location с приватными адресами

Почему опасно

Без proxy_redirect бэкенд может вернуть Location-заголовок с внутренним IP (например, 302 http://10.0.1.5:8080/login). Это раскрывает топологию внутренней сети — полезная информация для дальнейших атак.

Код

Уязвимо
location / {
    proxy_pass http://backend:3000;
    # внутренний IP может утечь
}
Правильно
location / {
    proxy_pass http://backend:3000;
    proxy_redirect default;
}
MEDIUM

autoindex onЛистинг директорий раскрывает структуру файлов

Почему опасно

autoindex on показывает все файлы в директории. Атакующий может найти backup-файлы, конфигурации, логи, временные файлы, которые не предназначены для публичного доступа.

Код

Уязвимо
autoindex on;
Правильно
# Удалите autoindex on
# Или ограничьте доступ
location /public-files/ {
    autoindex on;
    allow 10.0.0.0/8;
    deny all;
}
MEDIUM

Отсутствие security-заголовковXSS, clickjacking, MIME-sniffing

Почему опасно

Без заголовков безопасности сайт уязвим к: Clickjacking (X-Frame-Options), MIME-sniffing (X-Content-Type-Options), XSS (Content-Security-Policy), SSL stripping (Strict-Transport-Security).

Код

Уязвимо
server {
    # нет security-заголовков
}
Правильно
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Content-Security-Policy "default-src 'self'" always;
MEDIUM

Сброс заголовков в дочерних блокахadd_header в location стирает заголовки из server

Почему опасно

В nginx если в location есть хоть один add_header, ВСЕ заголовки из родительского server/http-блока сбрасываются. Security-заголовки (X-Frame-Options, HSTS) молча исчезают из ответа для этого location.

Код

Уязвимо
server {
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;

    location /api/ {
        add_header X-Api-Version "1.0";
        # ↑ X-Frame-Options и X-Content-Type
        #   теперь НЕ отдаются для /api/!
    }
}
Правильно
server {
    add_header X-Frame-Options DENY always;
    add_header X-Content-Type-Options nosniff always;

    location /api/ {
        # Повторяем все нужные заголовки
        add_header X-Frame-Options DENY always;
        add_header X-Content-Type-Options nosniff always;
        add_header X-Api-Version "1.0";
    }
}
MEDIUM

Нет limit_req / limit_connНет защиты от brute force и DDoS

Почему опасно

Без rate limiting атакующий может: перебирать пароли на /login без ограничений, отправлять тысячи запросов в секунду для DDoS, исчерпать соединения к бэкенду. Даже базовый limit_req значительно снижает эффективность brute force.

Код

Уязвимо
server {
    # нет limit_req / limit_conn
}
Правильно
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=conn:10m;

server {
    limit_conn conn 20;

    location /api/ {
        limit_req zone=api burst=20 nodelay;
    }
}

Низкие проверки

LOW
LOW

server_tokens не скрытВерсия nginx видна в заголовке Server и страницах ошибок

Почему опасно

Заголовок Server: nginx/1.18.0 раскрывает точную версию. Атакующий может подобрать эксплойт для конкретной версии (CVE). Это information disclosure — не уязвимость сама по себе, но упрощает разведку.

Код

Уязвимо
http {
    # server_tokens on по умолчанию
    # Ответ: Server: nginx/1.18.0
}
Правильно
http {
    server_tokens off;
    # Ответ: Server: nginx
}
# Или server_tokens build; для кастомной строки