24 июля исследователи из компании depthfirst опубликовали работающий PoC-код для уязвимости в GitLab. Патч на эту дырку вышел ещё 10 июня — то есть между исправлением и публичным эксплойтом прошло шесть недель. PoC собран под серверы self-managed GitLab версии 18.11.3, которые не обновились.
Самое неприятное в этой истории — порог входа для атакующего. Нужен всего лишь аутентифицированный пользователь, у которого есть право пушить в проект. Никаких прав администратора, доступа к CI или раннерам, взаимодействия с жертвой или доступа к чужим проектам не требуется вообще. Достаточно обычного разработчика с обычным доступом на запись.
Цепочка атаки строится так: сначала атакующий коммитит специально собранный Jupyter notebook, затем открывает diff этого коммита — и получает утечку указателя в куче. Повторные утечки позволяют автоматизированному пробнику найти нужные библиотеки в памяти. После этого срабатывают ещё два notebook с payload'ом, и в итоге команды выполняются от имени пользователя git.
Корень проблемы лежит в библиотеке Oj — JSON-парсере для Ruby, написанном в основном на C. Система depthfirst автономно обнаружила два бага, связанных с повреждением памяти, а исследователи вручную связали их в единую цепочку. Затрагиваемый компонент GitLab — ipynbdiff, встроенный гем для рендеринга notebook'ов. Он передаёт JSON из контролируемого репозиторием файла.ipynb в Oj::Parser.usual.parse, работая внутри долгоживущего Puma-воркера. Из-за этого байты, контролируемые атакующим, попадают прямо в память C, которой Oj управляет вручную, внутри процесса приложения.
Первый баг записывает данные за пределы фиксированного стека вложенности в 1024 байта — пока атакующий не получит контроль над start callback парсера. Второй баг обрезает ключ объекта длиной 65565 байт до 29 в поле знакового 16-битного целого, что возвращает живой указатель на кучу — и GitLab честно отрисовывает его в diff. Эта утечка позволяет найти libc, а запись направляет callback на функцию system().
GitLab не оформил это как исправление безопасности. Проверка The Hacker News показала: обновление Oj до версии 3.17.3 указано в релизе от 10 июня под заголовком «bug fixes», а не в таблице security-исправлений. CVE не назначен, CVSS-оценки нет, о цепочке через notebook-diff в релизных заметках ни слова. В итоге операторы, которые смотрели только на таблицу безопасности, не имели никаких оснований считать это срочным.
Затронуты версии GitLab CE/EE от 15.2.0 до 18.10.7 (фикс в 18.10.8), от 18.11.0 до 18.11.4 (фикс в 18.11.5), от 19.0.0 до 19.0.1 (фикс в 19.0.2). Гем Oj затронут в версиях от 3.13.0 до 3.17.1, исправление — в 3.17.3. Проблема касается всех тиров — от Free до Ultimate, в CE и EE. Сам Ruby не затронут. Версия Oj 3.17.2 содержала другие правки из того же обзора, но не эти два конкретных бага.
Рекомендуемое обновление — до 18.10.8, 18.11.5 или 19.0.2. Ни GitLab, ни depthfirst не предложили обходного решения. Исследователь depthfirst Юханг Ву заявил, что не знает о документированной опции GitLab для полного отключения уязвимого пути рендеринга notebook-diff. depthfirst также не проверила ни одного варианта конфигурации, который могла бы рекомендовать как рабочий обход. По его словам, теоретически удержание недоверенных пользователей от рендеринга notebook-diff убрало бы точку входа — но операторам, которые не могут обновиться, стоит запросить у GitLab конкретные рекомендации для своего развёртывания.
Отдельная ловушка касается пользователей Helm и Operator: проверять нужно версию GitLab внутри образа Webservice, где запущен Puma, а не версию чарта или Operator. Для версий с 15.2 по 18.9 бэкпорта не будет — эти релизы находятся за пределами поддерживаемых патч-трейнов GitLab, и таким инсталляциям остаётся только переход на поддерживаемый релиз.
Команды выполняются от имени git — учётной записи, за которой работает Puma. Реальный масштаб последствий зависит от изоляции конкретной инсталляции, но потенциально под угрозой оказываются исходный код, секреты Rails, учётные данные сервисов, данные CI/CD и любые внутренние сервисы, до которых способно дотянуться приложение.
PoC собран под GitLab 18.11.3 на архитектуре x86-64, с использованием конкретных offset'ов gadget'ов, состояния регистров и особенностей поведения jemalloc именно в этом образе. Есть и ограничение: восстановленный базовый адрес библиотеки живёт только до перезапуска Puma master. По словам Ву, путь эксплуатации не специфичен именно для 18.11.3 — перенос на другую сборку GitLab той же архитектуры обычно требует лишь небольших правок, вроде обновлённых offset'ов gadget'ов и символов. Эти offset'ы часто не меняются между близкими релизами, потому что gadget'ы берутся из библиотек Ruby и системных, а не из кода самого GitLab. Настоящий барьер — архитектура: переход на ARM64 потребовал бы других соглашений вызова функций, других gadget'ов, другого использования регистров и, возможно, другого поведения кучи. Версия сборки Ruby, libc, аллокатор и среда компиляции здесь важнее версии GitLab.
По замерам depthfirst, поиск в памяти на свежей инсталляции с двумя воркерами занимает 5–10 минут, а на дольше работающих системах — уже 1–2 часа. Полное описание цепочки приведено в отчёте depthfirst.
Хронология раскрытия выглядит так: 21 мая depthfirst сообщила о багах в Oj мейнтейнеру, 27 мая исправления были слиты, 4 июня вышел Oj 3.17.3. 5 июня GitLab получил отчёт о цепочке уязвимостей, 8 июня подтвердил проблему, а 10 июня выпустил патч.
Ву подтвердил, что depthfirst не запрашивала идентификаторы CVE ни для одной из проблем. Цепочка, касающаяся GitLab, прошла через программу HackerOne и не получила отдельного идентификатора. Два бага в Oj пошли прямо к мейнтейнеру библиотеки — CVE ни запрошен, ни назначен, насколько известно depthfirst. По формулировке самого Ву, отсутствие CVE отражает лишь способ координации раскрытия информации, а не степень серьёзности уязвимости.
depthfirst заявляет, что не располагает данными об эксплуатации этой уязвимости в реальных условиях. При этом GitLab самостоятельно воспроизвёл RCE. Ву оговаривается: этот вывод строится на отсутствии сообщений и на наблюдениях в собственной исследовательской среде depthfirst, а не на телеметрии — у компании нет доступа к внутренним данным GitLab или к клиентским средам. Именно GitLab, по его словам, остаётся «правильным источником» для выводов, основанных на такой информации.
Более широкий обзор Oj, проведённый depthfirst, породил ещё девять отдельных CVE-советов, но ни один из них не связан с описанной цепочкой.
GitLab до сих пор не ответил The Hacker News на вопросы о том, почему исправление не было классифицировано как проблема безопасности и будет ли для него всё же выпущен CVE.
26 июля 2026 года к материалу была добавлена правка. Первоначально утверждалось, что портирование эксплойта на другую версию требует «серьёзной работы». После уточнения формулировка изменилась: перенос на другую сборку GitLab той же архитектуры, как правило, требует лишь незначительного обновления offset'ов, а действительно существенная работа нужна только при переходе на другую архитектуру процессора.
Самое неприятное в этой истории — порог входа для атакующего. Нужен всего лишь аутентифицированный пользователь, у которого есть право пушить в проект. Никаких прав администратора, доступа к CI или раннерам, взаимодействия с жертвой или доступа к чужим проектам не требуется вообще. Достаточно обычного разработчика с обычным доступом на запись.
Цепочка атаки строится так: сначала атакующий коммитит специально собранный Jupyter notebook, затем открывает diff этого коммита — и получает утечку указателя в куче. Повторные утечки позволяют автоматизированному пробнику найти нужные библиотеки в памяти. После этого срабатывают ещё два notebook с payload'ом, и в итоге команды выполняются от имени пользователя git.
Корень проблемы лежит в библиотеке Oj — JSON-парсере для Ruby, написанном в основном на C. Система depthfirst автономно обнаружила два бага, связанных с повреждением памяти, а исследователи вручную связали их в единую цепочку. Затрагиваемый компонент GitLab — ipynbdiff, встроенный гем для рендеринга notebook'ов. Он передаёт JSON из контролируемого репозиторием файла.ipynb в Oj::Parser.usual.parse, работая внутри долгоживущего Puma-воркера. Из-за этого байты, контролируемые атакующим, попадают прямо в память C, которой Oj управляет вручную, внутри процесса приложения.
Первый баг записывает данные за пределы фиксированного стека вложенности в 1024 байта — пока атакующий не получит контроль над start callback парсера. Второй баг обрезает ключ объекта длиной 65565 байт до 29 в поле знакового 16-битного целого, что возвращает живой указатель на кучу — и GitLab честно отрисовывает его в diff. Эта утечка позволяет найти libc, а запись направляет callback на функцию system().
GitLab не оформил это как исправление безопасности. Проверка The Hacker News показала: обновление Oj до версии 3.17.3 указано в релизе от 10 июня под заголовком «bug fixes», а не в таблице security-исправлений. CVE не назначен, CVSS-оценки нет, о цепочке через notebook-diff в релизных заметках ни слова. В итоге операторы, которые смотрели только на таблицу безопасности, не имели никаких оснований считать это срочным.
Затронуты версии GitLab CE/EE от 15.2.0 до 18.10.7 (фикс в 18.10.8), от 18.11.0 до 18.11.4 (фикс в 18.11.5), от 19.0.0 до 19.0.1 (фикс в 19.0.2). Гем Oj затронут в версиях от 3.13.0 до 3.17.1, исправление — в 3.17.3. Проблема касается всех тиров — от Free до Ultimate, в CE и EE. Сам Ruby не затронут. Версия Oj 3.17.2 содержала другие правки из того же обзора, но не эти два конкретных бага.
Рекомендуемое обновление — до 18.10.8, 18.11.5 или 19.0.2. Ни GitLab, ни depthfirst не предложили обходного решения. Исследователь depthfirst Юханг Ву заявил, что не знает о документированной опции GitLab для полного отключения уязвимого пути рендеринга notebook-diff. depthfirst также не проверила ни одного варианта конфигурации, который могла бы рекомендовать как рабочий обход. По его словам, теоретически удержание недоверенных пользователей от рендеринга notebook-diff убрало бы точку входа — но операторам, которые не могут обновиться, стоит запросить у GitLab конкретные рекомендации для своего развёртывания.
Отдельная ловушка касается пользователей Helm и Operator: проверять нужно версию GitLab внутри образа Webservice, где запущен Puma, а не версию чарта или Operator. Для версий с 15.2 по 18.9 бэкпорта не будет — эти релизы находятся за пределами поддерживаемых патч-трейнов GitLab, и таким инсталляциям остаётся только переход на поддерживаемый релиз.
Команды выполняются от имени git — учётной записи, за которой работает Puma. Реальный масштаб последствий зависит от изоляции конкретной инсталляции, но потенциально под угрозой оказываются исходный код, секреты Rails, учётные данные сервисов, данные CI/CD и любые внутренние сервисы, до которых способно дотянуться приложение.
PoC собран под GitLab 18.11.3 на архитектуре x86-64, с использованием конкретных offset'ов gadget'ов, состояния регистров и особенностей поведения jemalloc именно в этом образе. Есть и ограничение: восстановленный базовый адрес библиотеки живёт только до перезапуска Puma master. По словам Ву, путь эксплуатации не специфичен именно для 18.11.3 — перенос на другую сборку GitLab той же архитектуры обычно требует лишь небольших правок, вроде обновлённых offset'ов gadget'ов и символов. Эти offset'ы часто не меняются между близкими релизами, потому что gadget'ы берутся из библиотек Ruby и системных, а не из кода самого GitLab. Настоящий барьер — архитектура: переход на ARM64 потребовал бы других соглашений вызова функций, других gadget'ов, другого использования регистров и, возможно, другого поведения кучи. Версия сборки Ruby, libc, аллокатор и среда компиляции здесь важнее версии GitLab.
По замерам depthfirst, поиск в памяти на свежей инсталляции с двумя воркерами занимает 5–10 минут, а на дольше работающих системах — уже 1–2 часа. Полное описание цепочки приведено в отчёте depthfirst.
Хронология раскрытия выглядит так: 21 мая depthfirst сообщила о багах в Oj мейнтейнеру, 27 мая исправления были слиты, 4 июня вышел Oj 3.17.3. 5 июня GitLab получил отчёт о цепочке уязвимостей, 8 июня подтвердил проблему, а 10 июня выпустил патч.
Ву подтвердил, что depthfirst не запрашивала идентификаторы CVE ни для одной из проблем. Цепочка, касающаяся GitLab, прошла через программу HackerOne и не получила отдельного идентификатора. Два бага в Oj пошли прямо к мейнтейнеру библиотеки — CVE ни запрошен, ни назначен, насколько известно depthfirst. По формулировке самого Ву, отсутствие CVE отражает лишь способ координации раскрытия информации, а не степень серьёзности уязвимости.
depthfirst заявляет, что не располагает данными об эксплуатации этой уязвимости в реальных условиях. При этом GitLab самостоятельно воспроизвёл RCE. Ву оговаривается: этот вывод строится на отсутствии сообщений и на наблюдениях в собственной исследовательской среде depthfirst, а не на телеметрии — у компании нет доступа к внутренним данным GitLab или к клиентским средам. Именно GitLab, по его словам, остаётся «правильным источником» для выводов, основанных на такой информации.
Более широкий обзор Oj, проведённый depthfirst, породил ещё девять отдельных CVE-советов, но ни один из них не связан с описанной цепочкой.
GitLab до сих пор не ответил The Hacker News на вопросы о том, почему исправление не было классифицировано как проблема безопасности и будет ли для него всё же выпущен CVE.
26 июля 2026 года к материалу была добавлена правка. Первоначально утверждалось, что портирование эксплойта на другую версию требует «серьёзной работы». После уточнения формулировка изменилась: перенос на другую сборку GitLab той же архитектуры, как правило, требует лишь незначительного обновления offset'ов, а действительно существенная работа нужна только при переходе на другую архитектуру процессора.