В популярнейшем веб- и прокси-сервере nginx была обнаружена занятная уязвимость: специально сформированным запросом можно получить информацию о внутренней структуре приложения. Этот баг томился без малого десять лет, и подвержены ей версии с 0.5.6 и до 1.13.2 включительно — то есть с 2007 года по июль 2017-го. Nginx, как известно, используется на каждом третьем-четвертом сайте, так что изучить эту лазейку не помешает.

WARNING

Материал адpесован специалистам по безопасности и тем, кто собираeтся ими стать. Вся информация предоставлена исключительно в ознакомительных целях. Ни редакция, ни автор не несут ответственности за любой возможный вред, причиненный материалами данной статьи.

 

Тестовый стенд

Для проведения экспериментов нам понадобится тестовая площадка. Конечно, всегда можно самому установить и настроить нужный дистрибутив, но зачем, когда можно просто взять и сразу перейти непосредственно к опытам? Тем более наши китайские коллеги уже собрали готовую уязвимую среду и выложили ее в виде докер-контейнера. Скачать его можно из репозитория vulapps. На странице полно иероглифов, так что вот тебе команда, которая нужна для запуска сервера:

docker run --rm --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -d -p 80:80 medicean/vulapps:n_nginx_1

Вообще, в этом репозитории ютится множество стендов для тестирования разных уязвимостей, советую изучить на досуге.

После успешного выполнения команды на 80-м порте у нас будет висеть подопытный веб-сервер nginx версии 1.13.1.

Если вдруг тебе захочется немного подебажить, то рекомендую приаттачиться к контейнеру и выполнить

apt-get update && apt-get install nano build-essential gdb nginx-dbg=1.13.1-1~stretch
service nginx stop && service nginx-debug start
ps -aux|grep nginx

Теперь можно присоединяться к процессу (worker process) с помощью gdb.

gdb --pid <pid>

Сразу предупреждаю, что импакт у уязвимости невесть какой, однако покопаться в ней интересно.

 

Немного о Range

Причина уязвимости — в некорректной обработке байтовых диапазонов в заголовке Range. Возможно, ты в курсе, что это такое, но давай на всякий случай повторим.

Базовая информация о заголовке Range
Базовая информация о заголовке Range

Заголовок Range используется, когда нужно получить не полный ответ от сервера, а только его часть. Формат допустимых значений заголовка подробно описан в спецификации протокола HTTP 1.1 RFC2616.

Согласно стандарту, в качестве значений можно указывать диапазоны для выборки. Они состоят из двух частей: размерность диапазона и список правил выборки. В качестве размерности используются байты.

Range: bytes=[-]<begin>-[<end>][,]

Саму выборку можно делать двумя способами. Первый — указать две позиции: начало выборки и ее конец. Причем стандарт четко определяет, что начальная позиция должна быть не меньше нуля, а конечная обязательно больше начальной или равна ей. Если это условие не соблюдается, то заголовок должен быть проигнорирован. Если в качестве последней позиции указано значение, которое больше размера запрашиваемого документа или равно ему, то последней позицией считается текущий размер документа в байтах минус один. То же самое касается тех случаев, когда конечная позиция не указана вообще.

К примеру, если размер документа 138 байт, то bytes=1-137 получит от сервера 137 байт информации, начиная со второго и заканчивая последним.

Первый способ выборки части контента с помощью Range
Первый способ выборки части контента с помощью Range

Помимо этого, в ответе присутствует заголовок Content-Range, где после слеша указан полный размер документа, который мы запрашиваем.

Второй способ — выборка последних N байт тела документа. Если размер документа меньше, чем указанный в запросе, то будет выбран весь документ. Например, bytes=-7 запрашивает последние 7 байт.

Второй способ выборки части контента с помощью Range
Второй способ выборки части контента с помощью Range

Также спецификация разрешает в одном заголовке Range указать несколько диапазонов, в качестве разделителя используется запятая.

Использование нескольких байтовых диапазонов в заголовке Range
Использование нескольких байтовых диапазонов в заголовке Range

Отмечу, что если ответ сервера содержит заголовок Accept-Ranges, то он поддерживает получение данных частями, когда в запросах есть хидер Range. Но, конечно же, это не говорит со стопроцентной вероятностью о его поддержке или ее отсутствии. Так что рекомендую все проверять на практике.

Наличие заголовка Accept-Ranges говорит о поддержке Range в запросах
Наличие заголовка Accept-Ranges говорит о поддержке Range в запросах
 

Углубляемся в детали

В nginx за обработку заголовка Range отвечает модуль ngx_http_range_header_filter_module, который вызывает функцию ngx_http_range_header_filter.

/src/http/modules/ngx_http_range_filter_module.c
146: static ngx_int_t
147: ngx_http_range_header_filter(ngx_http_request_t *r)
148: {

Если в пакете указан только один диапазон, то выводом информации занимается функция ngx_http_range_singlepart_header, а если несколько, то ngx_http_range_multipart_header.

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

Вариант 2. Открой один материал

Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    1 Комментарий
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии