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

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

alias: замена вместо приклеивания

Коротко

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

  • alias заменяет совпавшую часть URI на указанный путь и приклеивает остаток.
  • Для каталога заверши слешем и location, и alias. Без слеша у alias имена склеятся; без границы location возможен доступ к соседним файлам.
  • Совпадают хвосты пути и адреса - бери root: он безопаснее по конструкции.
  • В регулярном location alias собирают вручную из захватов.

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

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

Два конфига отличаются одним символом:

/etc/nginx/conf.d/shop.confhttp › server
location /files/ { alias /srv/uploads/; }    # А
location /files  { alias /srv/uploads/; }    # Б

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

Инстинктивный ответ - 404 в обоих: такого файла в каталоге загрузок нет. Настоящий: конфиг А отдаст 404, а конфиг Б отдаст содержимое db.env.

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

Разница между root и alias - это разница между приставкой и подстановкой.

root работает как приписка района к адресу: улица и дом остаются на месте. alias - как «вместо этого адреса читай вот такой»: часть адреса вычёркивается и заменяется другой.

Продолжим аналогию до ловушки. Замена сработает правильно, только если вырезали ровно то, что собирались. Вырезали «Ленина, 12» вместо «Ленина, 12,» - и оставшаяся запятая приклеится к новому адресу, дав бессмыслицу. В конфиге роль этой запятой играет слеш, а бессмыслица получается не всегда безобидная.

Механизм: что именно вырезается

/etc/nginx/conf.d/shop.confhttp › server
location /files/ {
    alias /srv/uploads/;
}
URI /files/ 2026/report.pdf совпало с location остаток URI alias: совпавшее ЗАМЕНЯЕТСЯ /srv/uploads/ 2026/report.pdf root: НИЧЕГО не вырезается, URI приклеивается целиком /srv/uploads /files/2026/report.pdf
Разница ровно в куске, совпавшем с location: root его сохраняет, alias съедает. Отсюда и правило выбора.

Правило выбора простое. Каталог на диске называется так же, как адрес в URL - бери root. Каталог называется иначе или лежит вне корня сайта - бери alias. Документация nginx говорит то же самое жёстче: если хвост пути совпадает с location, alias считается ошибкой стиля.

location /images/ { alias /data/w3/images/; }   # хвосты совпадают - лишний alias
location /images/ { root /data/w3; }            # то же самое, но без ловушек

Правило

Для отображения каталога используй префиксный location /имя/ и путь alias /каталог/ с завершающими слешами. Два пути без слеша тоже опасны: /img2/x совпадёт с /img и при alias /data превратится в /data2/x. Проверяй также адреса рядом с границей префикса.

Разбор: пропавший слеш у alias

Проверено на nginx 1.31.5, в заголовок выведен $request_filename.

/etc/nginx/conf.d/shop.confhttp › server
location /files/ { alias /srv/uploads/; }    # слеши парой
location /bad/   { alias /srv/uploads;  }    # слеша на конце нет
$  команда
for u in /files/2026/report.pdf /bad/2026/report.pdf; do echo "== $u"; curl -sI localhost$u | grep -E 'HTTP|X-Path'; done
↳  выводfor u in /files/2026/report.pdf /bad/2026/report.pdf; do echo "== $u"; curl -sI localhost$u | grep -E 'HTTP|X-Path'; done
== /files/2026/report.pdf
HTTP/1.1 200 OK
X-Path: /srv/uploads/2026/report.pdf
== /bad/2026/report.pdf
HTTP/1.1 404 Not Found
X-Path: /srv/uploads2026/report.pdf

Второй ответ объясняет всё. Остаток после /bad/ - это 2026/report.pdf, а alias без слеша даёт /srv/uploads. Склеили - получили /srv/uploads2026/, каталога с таким именем не существует.

Ошибки при nginx -t не будет: синтаксис безупречен. В ответ прилетает 404, и человек идёт проверять права, монтирование и опечатки в имени файла - вместо того чтобы открыть error_log, где путь написан целиком.

Разбор: где наивное правило подводит

Наивное правило: «за пределы каталога, указанного в alias, запрос не выйдет».

Обратная асимметрия - слеш есть у alias, но нет у location - уже не про 404:

/etc/nginx/conf.d/shop.confhttp › server
location /trav {              # без слеша!
    alias /srv/uploads/;
}
$  команда
curl -s -w '  код=%{http_code}\n' 'localhost/trav../secrets/db.env'
↳  выводcurl -s -w ' код=%{http_code}\n' 'localhost/trav../secrets/db.env'
СЕКРЕТ=123
  код=200

Разбор по шагам, и каждый шаг сам по себе законен:

URI:                     /trav../secrets/db.env
префикс - это строка:    /trav совпал (помнишь /app и /application.log?)
остаток:                 ../secrets/db.env
alias:                   /srv/uploads/
склейка:                 /srv/uploads/../secrets/db.env
файловая система:        /srv/secrets/db.env

Запрос вышел из каталога загрузок на уровень выше, к соседям по /srv, и nginx отдал файл совершенно законно: с его точки зрения его именно об этом и просили. Каждая лишняя пара ../ поднимает ещё на уровень.

Тут стоит сказать, чего эта дыра не отменяет. В четвёртом разделе мы проверяли, что URI нормализуется до выбора location: /files/../secrets/ туда не проходит. Всё верно - и именно поэтому атака пишется как /trav.., без слеша: /trav.. это одна часть пути, схлопывать в ней нечего. Нормализация делает свою работу честно, а дыру открывает несимметричная пара слешей.

Атака известна как alias traversal, ей больше десяти лет, и она до сих пор регулярно находится при аудитах: конфиг выглядит нормально ровно до тех пор, пока не сравнишь два символа.

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

  • 404 на файл, который точно лежит на месте. Смотри error_log или $request_filename: скорее всего, в пути склеились два куска без слеша.
  • Отдаются файлы из соседних каталогов. У location нет слеша, а у alias есть. Самая дорогая опечатка раздела.
  • alias в регулярном location не работает. Там нет «остатка», который можно приклеить, - путь собирают вручную из захватов:
/etc/nginx/conf.d/shop.confhttp › server
location ~ ^/avatars/(?<user>\w+)\.png$ {
    alias /srv/avatars/$user.png;
}

\w+ - одна или больше букв, цифр или знаков _, а имя группы user становится переменной $user, как с $tenant в разделе про server_name. Приём рабочий, но читается тяжело. Решается задача префиксным location - решай префиксным.

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

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

$  команда
nginx -T | grep -A1 -E 'location .*\{' | grep -B1 alias

-E - шаблон с | и .*, -A1 - показать ещё строку после найденной, -B1 - строку перед ней. Смотришь на пары строк и сверяешь концы. Всё, что нужно знать для этого ревью, - что слеши ходят парой; понимать логику приложения не требуется.

Вторая привычка того же порядка - предпочитать root. Он не умеет traversal по конструкции: вырезать ему нечего, значит и остатка с точками не возникнет. Раскладывая файлы на диске так, чтобы имя каталога совпадало с адресом, ты не «делаешь красиво», а убираешь целый класс ошибок.

Теперь сам

1. location /static/ { alias /var/www/dist/; }. Где будет искаться /static/js/app.js?

/var/www/dist/js/app.js. Совпавшее /static/ заменено на /var/www/dist/, остаток приклеен.

2. Как переписать конфиг из первого вопроса урока, чтобы дыры не было, и почему это лучше, чем просто дописать слеш?

location /files/ { root /srv; }    # если каталог на диске называется files

Дописать слеш к location /files - правильно и достаточно. Но если каталог на диске можно назвать так же, как адрес, то root убирает саму возможность ошибиться: у него нет пары слешей, которую надо держать симметричной.

3. Почему /files/../secrets/db.env не пробивает даже конфиг без слеша, а /files../secrets/db.env пробивает?

В первом случае .. - отдельная часть пути, и нормализация схлопывает её ещё до выбора location: nginx ищет /secrets/db.env и в блок загрузок не заходит. Во втором files.. - одна часть пути, схлопывать нечего, и точки доезжают до склейки с alias.

Главное

alias заменяет совпавший префикс URI путём каталога. Проверь границу этой замены: пропущенный слеш может склеить имена или разрешить выход в соседний каталог. Если структура URI уже совпадает с деревом файлов, root позволяет построить путь без такого преобразования.

Комментарии

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

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

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