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

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

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

Коротко

root складывает, alias заменяет. Одна эта фраза закрывает девять вопросов из десяти, а десятый стоит разбора: на нём теряют файлы и открывают дыры.

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

Если про 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 - либо оба есть, либо обоих нет. Ревью конфига начинай с этого: посмотрел на пару строк, сравнил концы.

Разбор: пропавший слеш у 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 на указанный путь, root приклеивает URI целиком. Слеши на концах location и alias ставятся парой: пропущенный у alias даёт молчаливый 404, пропущенный у location отдаёт файлы за пределами каталога. Совпадают хвосты - бери root, он безопаснее по конструкции.

Комментарии

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

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

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