Об одной уязвимости в…

duk3h5leha8sz3qinzb3g_8yfsk.jpeg

Год назад, 21 марта 2019, в баг баунти программу Mail.Ru на HackerOne пришел очень хороший багрепорт от maxarr. При внедрении нулевого байта (ASCII 0) в POST-параметр одного из API-запросов веб-почты, который возвращал HTTP-редирект, в данных редиректа виднелись куски неинициализированной памяти, в которых чаще всего раскрывались фрагменты из GET-параметров и заголовков других запросов к тому же серверу.
Это критическая уязвимость, т.к. запросы содержат в том числе сессионные куки. Через несколько часов был сделан временный фикс, который фильтровал нулевой байт (как потом выяснилось, этого было недостаточно, т.к. оставалась возможность инъекции CRLF /ASCII 13, 10, что позволяет манипулировать заголовками и данными HTTP-ответа, это менее критично, но все равно неприятно). Одновременно с этим проблема была передана аналитикам безопасности и разработчикам для поиска и устранения причин возникновения бага.

Почта Mail.ru — это очень непростое приложение, в формировании ответа может участвовать большое количество различных фронтенд/бекенд-компонентов, как опенсорсных (большое спасибо всем разработчикам свободного ПО), так и собственной разработки. Удалось исключить все компоненты кроме nginx и openresty и локализовать проблему до вызова ngx.req.set_uri () в OpenResty-скрипте, который вел себя не так, как ожидалось (воткнуть нулевой байт или перевод строки через GET-параметры с rewrite в ngx_http_rewrite_module, который, согласно документации используется и, казалось бы, должен работать абсолютно аналогично не получится). Были устранены возможные последствия, добавлена максимально строгая фильтрация и было проверено, что фильтрация устраняет все возможные векторы. Но механизм, который приводил к утечке содержимого памяти так и остался загадкой. Через месяц багрепорт закрыли как разрешенный, а разбор причин возникновения бага отложили до лучших времен.

OpenResty — это весьма популярный плагин, позволяющий писать Lua-скрипты внутри nginx, и он используется в нескольких проектах Mail.ru, поэтому проблема не считалась решенной. И спустя некоторое время к ней все-таки вернулись, чтобы понимать истинные причины, возможные последствия и составить рекомендации для разработчиков. В раскопках исходного кода участвовали Денис Денисов и Николай Ермишкин. Выяснилось что:

  • Nginx защищает GET параметры от инъекции служебных символов и дает возможность использовать в rewrite только GET параметры. Поэтому эксплуатировать инъекцию через контролируемые пользователем параметры в nginx не получается. POST параметры при этом не защищены. OpenResty позволяет работать и с GET и с POST параметрами, поэтому при использовании POST параметров через OpenResty появляется возможность инъекции специальных символов.
  • В nginx при использовании rewrite с пользовательскими данными есть возможность directory traversal (и вероятно SSRF) в некоторых конфигурациях, но это известный факт, и он должен обнаруживаться статическими анализаторами конфигураций в Nginx Amplify и Gixy от Яндекс (да, мы его тоже используем, спасибо). При использовании OpenResty такую возможность легко пропустить, но нашу конфигурацию это не затрагивало.
  • В nginx есть ошибка, приводящая к утечке содержимого памяти, если строка rewrite содержит нулевой байт. При отдаче редиректа nginx выделяет новый буфер памяти, соответствующий полной длине строке, но копирует туда строку через строчную функцию, поэтому строка копируется только до нулевого байта, остаток буфера содержит неинициализированные данные. Подробный разбор можно найти здесь.


Дальнейшая реакция


О проблеме сообщили разработчикам nginx и OpenResty, разработчики не рассматривает проблему как ошибку безопасности в nginx, т.к. в самом nginx нет возможности эксплуатации ошибки через инъекцию спецсимволов, фикс раскрытия содержимого памяти был опубликован 16 декабря. За 4 месяца с момента репорта в OpenResty так же не было сделано каких-либо изменений, хотя было понимание, что необходим безопасный вариант функции ngx.req.set_uri (). 18 марта 2020 мы опубликовали информацию, 21 марта OpenResty выпустила версию 1.15.8.3, которая добавляет проверку URI.

Portswigger написал хорошую статью и взял комментарии у OpenResty и Nginx (правда комментарий о том, что раскрывается только небольшой фрагмент памяти является неверной и вводит в заблуждение, это определяется длиной строки, следующей за нулевым байтом и, при отсутствии явных ограничений на длину может контролироваться атакующим).

Так в чем же была ошибка и что делать, чтобы ее предотвратить?


Была ли ошибка в nginx? Да, была, потому что утечка содержимого памяти это в любом случае ошибка.

Были ли ошибка в OpenResty? Да, как минимум не был исследован и документирован вопрос о безопасности предлагаемой OpenResty функциональности.

Была ли допущена ошибка конфигурации / использования OpenResty? Да, потому что в отсутствии явного указания, было сделано непроверенное предположение о безопасности используемой функциональности.

Какая из этих ошибок является ошибкой безопасности с баунти на $10000? Для нас это в общем-то не важно. В любом ПО, особенно на стыке нескольких компонент, особенно предоставляемых разными проектами и разработчиками никто и никогда не может гарантировать, что все особенности их работы известны и документированы и отсутствуют ошибки. Поэтому любая ошибка безопасности возникает именно там, где она влияет на безопасность.

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

Errata


По опыту предыдущей статьи, ради сохранения чистоты языка:

баг баунти — конкурс охоты за ошибками
багрепорт — уведомление об ошибке
редирект — перенаправление
опенсорсный — с открытым кодом
errata — работа над ошибками

© Habrahabr.ru