# теория · шаг 2 из 5
alias: замена вместо приклеивания
Коротко
root складывает, alias заменяет. Одна эта фраза закрывает девять вопросов
из десяти, а десятый стоит разбора: на нём теряют файлы и открывают дыры.
aliasзаменяет совпавшую часть URI на указанный путь и приклеивает остаток.- Слеши на концах
locationиaliasставятся парой. Нет слеша уalias- молчаливый 404. Нет слеша уlocation- отдача чужих файлов. - Совпадают хвосты пути и адреса - бери
root: он безопаснее по конструкции. - В регулярном location
aliasсобирают вручную из захватов. $request_filenameпоказывает итоговый путь и заканчивает любой спор о том, куда делся слеш.
Если про alias-traversal знаешь и слеши сверяешь парой - листай до «Теперь сам».
Сначала ответь сам
Два конфига отличаются одним символом:
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,» - и оставшаяся запятая приклеится к новому адресу, дав бессмыслицу. В конфиге роль этой запятой играет слеш, а бессмыслица получается не всегда безобидная.
Механизм: что именно вырезается
location /files/ {
alias /srv/uploads/;
}
Правило выбора простое. Каталог на диске называется так же, как адрес в URL -
бери root. Каталог называется иначе или лежит вне корня сайта - бери alias.
Документация nginx говорит то же самое жёстче: если хвост пути совпадает с
location, alias считается ошибкой стиля.
location /images/ { alias /data/w3/images/; } # хвосты совпадают - лишний alias
location /images/ { root /data/w3; } # то же самое, но без ловушек
Правило
Слеш на конце location и слеш на конце alias - либо оба есть, либо обоих
нет. Ревью конфига начинай с этого: посмотрел на пару строк, сравнил концы.
Разбор: пропавший слеш у alias
Проверено на nginx 1.31.5, в заголовок выведен $request_filename.
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
== /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:
location /trav { # без слеша!
alias /srv/uploads/;
}
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 не работает. Там нет «остатка», который можно приклеить, - путь собирают вручную из захватов:
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 на указанный путь, root приклеивает URI
целиком. Слеши на концах location и alias ставятся парой: пропущенный у
alias даёт молчаливый 404, пропущенный у location отдаёт файлы за пределами
каталога. Совпадают хвосты - бери root, он безопаснее по конструкции.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий