Алгоритм выбора location в Nginx

habr.png

Алгоритм выбора location обязателен к знанию при настройке nginx. Тем не менее, на официальном сайте nginx (на 2018 год) не сказано ни слова про алгоритм выбора в случаях, когда какие-то location’ы вложены друг в друга, а в статьях в интернете приводятся в корне неверные алгоритмы. Поэтому решил написать свою небольшую заметку.

Если Вы не знали о том, что кроме спуска по дереву вложенных location nginx также делает и подъём по дереву, статья обязательна к прочтению. В статье также будет дан пример уязвимого конфига.

Частный случай с одним уровнем вложенности
Если Вы новичок в nginx, то следует рассмотреть вначале частный случай без использования вложненных location, т. к. алгоритм для частного случая значительно проще:

  1. Вначале будет искаться равенство. Оно имеет высший приоритет.
  2. Потом будет искаться максимальный префиксный location, после чего будет проверено, есть ли на нём модификатор приоритета (^~), и если он есть, то будет возвращён этот location.
  3. Потом будут проверяться регулярные выражения сверху вниз. При первом же совпадении будет возвращён совпавший location.
  4. Потом вернётся тот префиксный location, который мы нашли до этого.


Обратите внимание, что этот алгоритм неприменим при наличии вложенных location.

Общий случай с вложенными location

  1. Стартуем с верхнего уровня.
  2. Если на текущем уровне выполняется равенство (=), поиск прекращается — это и будет результат, т. к. такой location не может иметь никаких других вложенных location.
  3. В противном случае ищем на текущем уровне самый большой префиксный location (( ) или (^~)).
    • Если такой префиксный location существует, то делаем его текущим уровнем и переходим к п. 2.
    • В противном случае выходим из цикла.
  4. Мы вышли из цикла. На данный момент мы нашли «самый большой» префиксный location, но не думайте, что это самый большой из всех. Пример:
    location /abc {
    	location /abcdefghi {
    		…
    	}
    }
    
    location /abcdef {
    	…
    }
    

    В данном примере мы перейдём в /abcdef, т. к. на его уровне он переборол более короткий /abc. Но по факту существуют location и больше него.
  5. Теперь в найденном location мы ищем первый верный regexp. При нахождении поиск полностью прекращается. Обратите внимание: в этом пункте мы по факту ищем regexp на самом нижнем уровне, а не на верхнем, как многие могли бы подумать. Т. е. поиск regexp идёт снизу, а не сверху (но внутри одного уровня идёт сверху, а не снизу).
    • Далее, если ничего не найдено, поднимаемся на один уровень вверх и аналогично ищем первый regexp, но в этот раз уже только при условии, что location, в котором мы были до этого, не имел метки (^~). Повторяем этот пункт до тех пор, пока подниматься будет некуда.
    • При этом нужно иметь ввиду:
      • Даже если какой-то из уровней имеет метку (^~), это не значит, что мы не осуществляем подъём. Подъём осуществляется всегда, но если более нижний уровень имел метку (^~), то на текущем уровне поиск regexp’ов не проводится.
      • Возможности запретить проверку regexp в самом нижнем уровне нет — для этого нужно создать ещё один вложенный уровень. А вот запретить проверку regexp на нулевом уровне можно — для этого location первого уровня (который находится на нулевом уровне) должен иметь метку (^~).
  6. Мы сделали подъём по дереву, но так и не нашли ни одного regexp. Раз regexp не найден, возвращаем «почти самый большой» префиксным location, который был найден ранее. Готово.

Также при этом:

  • В версиях 0.7.1–0.8.41 префиксный location ( ) при точном совпадении действует как =

Пример уязвимого конфига

location ~ \.php$ {
	deny all; # Здесь должно быть проксирование на php-fpm
}

location /posts/ {
	location ~ (.*)_2x(\.[a-z]+)$ {
		try_files $uri $1$2 =404;
	}
}


В данном конфиге мы настроили игнорирование »_2x», если файл не найден. Например, nginx попробует найти файл /posts/img/a_2x.png как по указанному пути, так и по пути /posts/img/a.png. Но в реальности, если мы запросим /posts/authData_2x.php, то мы получим исходный текст скрипта authData.php в голом виде. Чтобы избежать таких ошибок, нужно знать, как обрабатывается location в nginx.

Также дополнительной защитой может являться хранение скриптов в отдельной директории, недоступной из под обычных location. В этом случае, если наш location на php по каким-то причинам не сработает, пользователь получит ошибку 404, а не исходный текст скрипта.

Перенаправление location

  1. Если try_files не содержит кода ошибки последним параметром, то будет сделано перенаправление в другой location, т. к. последний параметр всегда делает перенаправление. Обратите внимание: код ошибки в try_files должен писаться через равно (=).
  2. index и error_page при срабатывании всегда делают перенаправление в другой location. Также перенаправление делает rewrite, если добавить в него флаг last.


Другое

  1. При выборе location не учитывается строка запроса, которая начинается со знака »?».


Отказ от ответственности
Алгоритм в статье составлен мной на основе моих личных наблюдений, и не факт, что он является правильным. К сожалению, официальной документации нет, а исходники я не читал. Если кто-то найдёт ошибки в алгоритме, просьба написать личным сообщением или в комментариях.

© Habrahabr.ru