В популярнейшем веб- и прокси-сервере nginx была обнаружена занятная уязвимость: специально сформированным запросом можно получить информацию о внутренней структуре приложения. Этот баг томился без малого десять лет, и подвержены ей версии с 0.5.6 и до 1.13.2 включительно — то есть с 2007 года по июль 2017-го. Nginx, как известно, используется на каждом третьем-четвертом сайте, так что изучить эту лазейку не помешает.
Материал ад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 используется, когда нужно получить не полный ответ от сервера, а только его часть. Формат допустимых значений заголовка подробно описан в спецификации протокола HTTP 1.1 RFC2616.
Согласно стандарту, в качестве значений можно указывать диапазоны для выборки. Они состоят из двух частей: размерность диапазона и список правил выборки. В качестве размерности используются байты.
Range: bytes=[-]<begin>-[<end>][,]
Саму выборку можно делать двумя способами. Первый — указать две позиции: начало выборки и ее конец. Причем стандарт четко определяет, что начальная позиция должна быть не меньше нуля, а конечная обязательно больше начальной или равна ей. Если это условие не соблюдается, то заголовок должен быть проигнорирован. Если в качестве последней позиции указано значение, которое больше размера запрашиваемого документа или равно ему, то последней позицией считается текущий размер документа в байтах минус один. То же самое касается тех случаев, когда конечная позиция не указана вообще.
К примеру, если размер документа 138 байт, то bytes=1-137
получит от сервера 137 байт информации, начиная со второго и заканчивая последним.
Помимо этого, в ответе присутствует заголовок Content-Range
, где после слеша указан полный размер документа, который мы запрашиваем.
Второй способ — выборка последних N байт тела документа. Если размер документа меньше, чем указанный в запросе, то будет выбран весь документ. Например, bytes=-7
запрашивает последние 7 байт.
Также спецификация разрешает в одном заголовке Range указать несколько диапазонов, в качестве разделителя используется запятая.
Отмечу, что если ответ сервера содержит заголовок Accept-Ranges
, то он поддерживает получение данных частями, когда в запросах есть хидер Range. Но, конечно же, это не говорит со стопроцентной вероятностью о его поддержке или ее отсутствии. Так что рекомендую все проверять на практике.
В nginx за обработку заголовка Range отвечает модуль ngx_http_range_header_filter_module
, который вызывает функцию ngx_http_range_header_filter
.
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
.
Cтатьи из последних выпусков журнала можно покупать отдельно только через два месяца после публикации. Чтобы читать эту статью, необходимо купить подписку.
Подписка позволит тебе в течение указанного срока читать ВСЕ платные материалы сайта, включая эту статью. Мы принимаем оплату банковскими картами, электронными деньгами и переводами со счетов мобильных операторов. Подробнее о подписке
1 год3200 р. Экономия 1400 рублей! |
1 месяц490 р. 25-30 статей в месяц |
Уже подписан?
Читайте также
Последние новости