1. 1
  2. 2
  3. 3
  4. 4
  5. 5

# теория · шаг 1 из 5

alias, слеш и путь наружу

Коротко

Одна дыра, которую находят сканером за секунду и правят одним символом. location /trav без слеша вместе с alias открывает соседние каталоги.

  • Замер: /trav../secrets/db.env вернул 200 и содержимое файла из каталога, которого в конфиге нет вовсе.
  • Со слешем (location /trav/) тот же запрос даёт 404.
  • Нормализация URI тут не помогает: .. стоит внутри одного сегмента, схлопывать нечего.
  • Правило шире одной директивы: у alias слеши держат парой, как и у proxy_pass.

Если помнишь про alias без слеша и умеешь проверить его одной командой - листай до «Теперь сам».

Сначала ответь сам

/etc/nginx/conf.d/shop.local.confhttp › server
location /trav {
    alias /var/www/uploads/;
}

Рядом с uploads лежит каталог secrets с файлом db.env. Что вернёт запрос /trav../secrets/db.env?

$  команда
curl http://shop.local/trav../secrets/db.env
↳  выводcurl http://shop.local/trav../secrets/db.env
пароли

Файл. Целиком - curl печатает его тело. Код при этом 200 (виден через -o /dev/null -w '%{http_code}'), а со слешем в location тот же запрос даёт 404 - разница в одном символе.

На что это похоже

Табличка на двери «склад» и правило: всё, что написано после слова «склад», дописывать к пути /дом/склад/. Посетитель пишет на бумажке «склад../сейф» - получается /дом/склад/../сейф, то есть /дом/сейф.

Ошибка не в посетителе, а в том, что слово «склад» разрешили продолжать. Слеш в конце - это и есть точка, после которой продолжать нельзя.

Механизм: где склеивается путь

location /trav (без слеша) /trav ../secrets/db.env склеиваем: /var/www/uploads/ + ../secrets/db.env = /var/www/secrets/db.env location /trav/ (со слешем) хвост начинается после слеша, и «..» уже не приклеивается к каталогу файла /var/www/uploads/../secrets/db.env по такому пути не находится - 404
Дыра живёт в склейке строк, а не в обработке пути.

Нормализация URI из четвёртого раздела тут не спасает: она схлопывает /a/../b в /b, а здесь точки стоят внутри одного сегмента - /trav.. - и схлопывать нечего. Именно поэтому атака и записывается без слеша перед точками.

Правило

/etc/nginx/conf.d/shop.local.confhttp › server
location /uploads/ {          # слеш в location
    alias /var/www/uploads/;  # и слеш в alias
}

Слеши держат парой - то же правило, что у proxy_pass из седьмого раздела. Если слеш в location не нужен по смыслу маршрута, вместо alias берут root:

/etc/nginx/conf.d/shop.local.confhttp › server
location /uploads {
    root /var/www;            # даст /var/www/uploads/...
}

root складывает путь целиком и такой дыры не имеет вовсе: он не вырезает совпавшую часть, а дописывает URI к корню.

Разбор: как проверить свой конфиг

$  команда
curl -s -o /dev/null -w '%{http_code}\n' https://shop.local/uploads../etc/passwd

404 или 403 - хорошо. 200 - у тебя дыра, и чинится она одним символом.

Найти кандидатов в чужом конфиге можно за секунду:

$  команда
nginx -T | grep -B2 alias | grep -E 'location [^/]*[^/]$'

Смотреть надо на все блоки с alias, у которых location не кончается слешем. Их обычно один-два, и оба - наследие копирования.

Разбор: что ещё вытекает через alias

Вторая беда той же директивы - разъехавшиеся слеши без всякой атаки:

/etc/nginx/conf.d/shop.local.confhttp › server
location /static/ {
    alias /var/www/assets;     # слеша нет
}

Запрос /static/app.js превращается в /var/www/assetsapp.js. В логе будет честное open() "/var/www/assetsapp.js" failed (2: No such file or directory), и по нему причина видна сразу - но только если в лог заглянуть.

Третья - alias внутри регулярного location. Там подстановка идёт по группам, и без явного указания хвоста получается путь, который тебе не нужен:

/etc/nginx/conf.d/shop.local.confhttp › server
location ~ ^/img/(.+)$ {   # (.+) захватывает хвост пути в $1
    alias /var/www/pictures/$1;   # хвост подставляем сами
}

Без $1 такой блок отдал бы один и тот же файл на любой запрос.

Что ломается без этого

Дыра не абстрактная: curl со строкой /trav../ есть в любом сканере, и проверяют её первым делом вместе с .git и .env из следующего урока. Достаётся оттуда обычно не /etc/passwd (nginx работает не от root и его чаще всего не прочитает), а гораздо более полезное для нападающего: конфиги приложения этажом выше, .env с паролем к базе, бэкапы, ключи API.

Поэтому правильная реакция на находку - не только починить слеш, но и считать утёкшим всё, что лежало в соседних каталогах: пароли меняют, ключи перевыпускают.

Зачем это в работе

Ревью конфига на эту тему занимает минуту и делается по трём вопросам:

  1. Есть ли alias там, где location без слеша?
  2. Есть ли alias, у которого слеш только с одной стороны?
  3. Можно ли вообще заменить alias на root?

Третий вопрос самый полезный. alias нужен, когда путь на диске не совпадает с адресом; если совпадает - root проще и безопаснее. В большинстве конфигов alias стоит просто по привычке.

Теперь сам

В конфиге location /files { alias /srv/files/; }. Коллега предлагает исправить на location /files/ { alias /srv/files/; }, но замечает, что тогда сломается адрес /files без слеша, по которому у них ходит старый клиент. Что делать?

Исправлять слеш обязательно, а старый адрес поддержать отдельной строкой: location = /files { return 301 /files/; }. Точное совпадение обрабатывается раньше префиксов, старый клиент получает редирект, дыра закрыта. Оставлять location /files с alias ради совместимости нельзя - это ровно та конфигурация, которую находят сканером.

Главное

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

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.