Исследователь Юхан Ву (Depthfirst) в рамках инициативы Open Defense обнаружил эксплойт-цепочку, позволяющую получить удалённое выполнение кода на стандартной инсталляции GitLab через два дефекта безопасности памяти в JSON-парсере Oj — высокопроизводительном нативном C-парсере, широко используемом в Ruby-приложениях, включая GitLab.
Автоматизированная система анализа выявила 18 приоритетных уязвимостей в Oj, семь из которых оказались memory-safety ошибками. Две из них незаметно сохранялись в коде почти пять лет, прежде чем были объединены в работающую эксплойт-цепочку.
Две уязвимости по отдельности
Первая: неконтролируемая запись на стек в Oj::Parser.usual.parse — не проверявшаяся глубина вложенности JSON позволяла переполнить стек.
Вторая: небезопасное сужение 16-битного ключа длины, раскрывающее указатель на кучу.
По отдельности каждая была ограничена: первая давала только повторяющийся примитив «записи одного байта», вторая раскрывала фиксированный 29-байтовый фрагмент памяти. Однако при аккуратном управлении кучей они вместе дали контроль над callback-указателем и способ обхода ASLR, что в итоге позволило выполнить произвольный код от имени системного пользователя git.
Как работает доставка
GitLab отображает различия для Jupyter Notebook (.ipynb) файлов через встроенный gem ipynbdiff. Перед генерацией diff gem парсит каждую ревизию notebook с помощью нативного парсера Oj для проверки наличия поля cells.
Поскольку notebook-файл — это JSON-документ, любой аутентифицированный пользователь, способный пушить коммиты и просматривать diff коммита, мог встроить вредоносные JSON-структуры в этот вызов.
Эксплойт работал следующим образом: пользователь отправлял два специально подготовленных notebook-файла в одном запросе на сравнение коммитов. Первый файл чрезмерной глубиной вложенности активировал неконтролируемую запись на стек, перенаправляя внутренний буферный указатель. Последующие операции с кучей вызывали перекрытие Ruby-массива с callback-указателем парсера, что позволяло атакующему записать выбранный адрес в p->start.
Второй файл выносил утечку указателя кучи в oversized-ключ JSON-объекта, который рендерился в HTML-вывод diff'а. Так атакующий получал адрес, необходимый для вычисления расположения libc и libruby в памяти, обходя ASLR.
Поскольку Puma (Ruby-сервер GitLab) использует несколько потоков, разделяющих один экземпляр нативного парсера на worker, оба файла обрабатывались одним уязвимым парсером в рамках одного запроса. Второй файл активировал повреждённый callback и исполнял команду через system().
Отличие от предыдущих уязвимостей
В отличие от более ранних проблем RCE в GitLab, полагавшихся на SSRF против внутреннего Redis, эта цепочка обходит современные SSRF-защиты GitLab полностью, атакуя нативную зависимость с небезопасной памятью внутри Ruby-приложения.
Источник: cybersecuritynews.com