РАЗБОР Статья

Разбор CVE-2026-97229 или как .gitlab-ci.yml роняет сервер

Михаил Гудилин
Михаил Гудилин Script Kiddie · 19 сообщений
Подписаться
72
[ обложка статьи ]
Режим чтения
Обложка: волна светящегося кода с ноутбука бьёт взрывом по серверной стойке в тёмном серверном зале


Что вас ждёт в статье:

Краткая сводка​

Десятка по CVSS - штука редкая. Чтобы её заслужить, уязвимость должна бить удалённо, без логина, без кликов жертвы и без хитрых предусловий. CVE-2026-97229 набрала всё это разом. И самое любопытное тут - точкой входа стала безобидная на вид вещь - регулярные выражения, которые мы пишем в конфигах пайплайнов. Триггером, правда, служит не любая регулярка, а специально собранная, да ещё в связке с хитро устроенным путём репозитория - руками такое случайно не наберёшь. Но сам факт, что безопасность CI/CD-сервера висит на разборе строки из .yml, уже отрезвляет.

Баг живёт в компиляторе регулярок внутри GitLab. Специально собранное выражение вызывает переполнение при расчёте размера скомпилированной программы, а дальше через цепочку некорректных инструкций дело доходит до выполнения чужого кода прямо на сервере. А сервер GitLab - это исходники, секреты пайплайнов, ключи деплоя, доступы в облако. Выходит, что джекпот для атакующего)

Нашёл дыру Johan Carlsson под ником joaxcar, сдал её через HackerOne, после чего GitLab 12 октября 2026 выкатил патч сразу в трёх ветках. Облачный GitLab.com прикрыли ещё раньше, а Dedicated трогать и вовсе не нужно. Так что под ударом оказались те, кто держит GitLab у себя, - self-managed-инсталляции, о которых дальше в основном и пойдёт речь. Разберём, как эта штука работает и как её ловить, но без раздела о том, как эксплуатировать, только матчасть и защита, а короткую сводку можно найти сразу под этим абзацем:
ИдентификаторCVE-2026-97229
Класс (CWE)CWE-190 - переполнение целого в компиляторе регулярных выражений CI/CD
ПродуктGitLab CE и EE, self-managed (GitLab.com и Dedicated не затронуты)
Уязвимые версии19.2 до 19.2.8, 19.3 до 19.3.5, 19.4 до 19.4.2
Исправлено в19.4.2 / 19.3.5 / 19.2.8 (12 октября 2026)
CVSS10.0 Critical - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
СтатусБез авторизации при ряде условий; эксплуатации «в полях» нет, публичного PoC нет
НашёлJohan Carlsson (joaxcar) через HackerOne
Хорошая новость: на момент разбора атак в реальных условиях не видели, рабочего публичного PoC тоже нет. Плохая - десятка не прощает промедления, а тем же релизом закрыли ещё пять High-багов, а с ними 14 Medium и 4 Low.

1. Откуда в CI/CD вообще регулярки​

Давайте начнём с азов - что такое CI/CD и зачем оно. Наверняка, не раз душили этим вопросом на собесе, так что своими словами. Это конвейер, который сам собирает, проверяет и выкатывает твой код. CI - непрерывная интеграция, т.е. закоммитил, а система сама прогнала сборку и тесты, не дожидаясь, пока кто-то про них вспомнит. CD - непрерывная доставка: прошло проверки - поехало на сервер, часто вообще без ручных действий. Смысл - убрать человека из рутины между написанием кода и кручения кода в проде. И вся эта машинерия описывается одним файлом в корне репозитория - .gitlab-ci.yml. GitLab читает его и понимает, какие джобы, в каком порядке и при каких условиях запускать. Удобно это ровно до того момента, пока тот же файл не становится точкой входа для атакующего. Если программист хоть раз настраивал пайплайн, то писал и сам регулярки, хотя-бы когда запускал джобу только для релизных веток, когда ловил теги определённого вида или отфильтровывал пути файлов. Всё это - регулярные выражения, которые живут в .gitlab-ci.yml, в правилах вроде rules:if и им подобных, и которые GitLab честно пытается разобрать.
Схема: путь регулярки из .gitlab-ci.yml через парсер конфига к багу в компиляторе регулярок


Суть в том, что регулярка в конфиге - это данные, которыми управляет тот, кто запушил коммит, а не настройка от доверенного админа. Любой ввод, попадающий в парсер, надо проверять - и GitLab, казалось бы, проверяет. Проблема в том, что проверяет он не совсем то, что потом исполняет, но об этом в следующем разделе.

2. Как число ломает всё: CWE-190​

Теперь самое интересное - почему безобидная на вид регулярка роняет сервер. И тут не всё так банально, как посчитали размер с переполнением и записали за буфер. Механика тоньше, и красота именно в ней.

Сначала GitLab компилирует регулярку в компактной US-ASCII-форме и проверяет её - валидация проходит, всё чисто. А дальше в игру вступает Onigmo, движок регулярок в Ruby, и перекомпилирует выражение уже под UTF-8. Вот ключевой момент: перекомпиляция случается уже ПОСЛЕ проверок. То есть проверяли одну форму регулярки, а исполняется - другая, которую никто толком не валидировал.

На этой-то второй компиляции и срабатывает CWE-190 - переполнение целого при расчёте размера программы регулярки. Из-за него движок генерирует некорректные инструкции: скомпилированное выражение превращается в кашу, которой там быть не должно.
Схема атаки: двойная компиляция регулярки в US-ASCII и UTF-8, переполнение CWE-190 и выполнение команд ОС


Дальше - потеря целостности потока управления. Эти некорректные инструкции позволяют протащить свои операции в виртуальную машину Onigmo прямо внутри Ruby-процесса, и история раскручивается до выполнения команды операционной системы. Спусковым крючком, кстати, выступает специально сформированный, но валидный UTF-8-путь репозитория, который прогоняется через регулярку, - отсюда и то самое требование к не-ASCII-символам в пути, без которого вектор не взлетает.

И важно про уровень проблемы. Здесь ломается сама логика исполнения на низком уровне, и в худшем случае это даёт выполнение произвольного кода. Отсюда и десятка.

3. «Unauth» со звёздочкой​

В карточке CVE стоит PR:N - привилегии не нужны, то есть формально это неаутентифицированная атака. Но давайте разберёмся честно, потому что нюанс тут важный и за красивой десяткой легко не заметить деталь.

Rapid7, разбирая баг, уточнили условия: атакующему нужно суметь закоммитить вредоносный .gitlab-ci.yml и путь репозитория с UTF-8-символами в ветку публичного проекта. На self-managed-инстансах, где обычным пользователям разрешено создавать публичные проекты, эту планку берёт любой зарегистрированный аккаунт. А регистрация часто открыта.

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

4. Почему это критично​

Итог эксплуатации - выполнение произвольного кода на самом сервере GitLab: не в песочнице раннера и не в контейнере отдельной джобы, а прямо на центральном узле, которому доверяет вся остальная инфраструктура. Одна оговорка для точности: код исполняется в рабочем процессе приложения от имени системного пользователя git, а не от root. Полного захвата машины с первого шага это не даёт, но для GitLab и прав git за глаза.

Почему это так больно, если вдуматься. GitLab - это сердце разработки. В нём лежат исходники всех проектов. В нём хранятся секреты CI/CD: токены, пароли, ключи для деплоя, доступы в облачные аккаунты. К нему подключены раннеры и реестры образов. Захватив GitLab, атакующий получает трамплин в весь конвейер поставки ПО - классическая supply-chain-история, когда через билд-систему дотягиваются до продакшена и до клиентов.

CVSS 10.0 это и фиксирует: удар по конфиденциальности, целостности и доступности разом (C:H/I:H/A:H), да ещё и с выходом за границы системы - флаг S:C в векторе как раз про то, что скомпрометированный GitLab утягивает за собой и то, что вокруг него.

5. Как это ловить​

Готовых индикаторов именно под эту CVE нет, детали issue-трекера GitLab придержит на 90 дней, чтобы все успели обновиться. Rapid7, правда, уже раскрыл суть первопричины и обещает отдельный технический разбор - так что кое-какое понимание механики у нас есть уже сейчас. Но конкретную сигнатуру никто пока не выдаст, поэтому охотимся по условиям доставки и по последствиям.
Схема детекта: признаки доставки атаки слева и последствия справа от сервера GitLab


Я бы смотрел на следующие моменты:

На свежие аккаунты и саморегистрации. Вектору нужен аккаунт, который создаёт публичный проект и пушит в него. Всплеск саморегистраций, за которым сразу идёт создание публичного проекта, - уже повод присмотреться.

Файл .gitlab-ci.yml и странные пути. Пуши .gitlab-ci.yml от свежих аккаунтов, особенно в проекты, где в пути репозитория попадаются не-ASCII-символы, - прямой отголосок условий эксплуатации.

Дочерние процессы сервера. Процесс GitLab от пользователя git, внезапно порождающий оболочку или сетевые утилиты, - один из самых честных сигналов RCE. Телеметрия запуска процессов тут главный друг.

Новые сущности с правами. Внезапно появившиеся пользователи, токены, deploy-keys, раннеры, хуки, интеграции. После захвата атакующий закрепляется именно через них.

Трафик и секреты. Необычные исходящие соединения с сервера и обращения к CI/CD-переменным и секретам, которых по логике быть не должно.

Логика такая же, как в любом поведенческом детекте - мы не ищем конкретный payload, ловим цепочку подозрительная доставка → посторонний процесс → новые доступы → отток данных. Готовых WAF-правил от вендора я не встречал - это, честно говоря, скорее мой вывод, чем подтверждённый факт, так что правила придётся собирать самому по своему трафику.

6. Защита и патч​

Тут всё коротко: обновляться. Полноценного обходного пути, который чинит сам баг, вендор не даёт - вылечит только патч.
  1. Обновить self-managed GitLab до 19.4.2, 19.3.5 или 19.2.8. Один апдейт закрывает не только эту дыру, но и пять других High-багов из того же релиза (а заодно 14 Medium и 4 Low).
  2. Брать именно 19.3.5, а не 19.3.4: промежуточный релиз притащил отдельную проблему со страницей merge request - правда, она вылезала только при включённой интеграции Ona.
  3. Проверить реальную версию запущенного приложения, особенно в контейнерах и Helm: номер чарта и версия GitLab - не одно и то же, легко обмануться.
Временные меры, пока не обновились:
  • ограничить анонимный доступ к публичным проектам;
  • запретить недоверенным пользователям создавать и писать в публичные проекты;
  • отключить ненужную публичную саморегистрацию.
Эти меры перекрывают лишь известный путь доставки, а сам уязвимый компилятор остаётся на месте. Так что без обновления всё равно не обойтись. Глобально, стоит сегментировать GitLab от чувствительной обвязки, в т.ч. раннеров, реестров, облака, внутренних сервисов. Чтобы захват сервера не превращался автоматически в захват всего конвейера.

Не имеет смысла ждать технических деталей, чтобы понять, насколько всё серьёзно, ведь к моменту их полного раскрытия патч давно должен стоять. Десятка по CVSS - сама по себе исчерпывающая мотивация.

Заключение​

Мораль даже тут про то, что мы называем вводом. Регулярку в конфиге пайплайна никто не воспринимает как опасные данные от постороннего - это же просто строчка в .yml, часть настройки. А оказалось - полноценный ввод, который доезжает до низкоуровневого кода и ломает ему логику исполнения. Переполнение целого - баг, которому не одно десятилетие. Казалось бы, где современный DevOps-комбайн, а где арифметика на переполнении счётчика. Но пока код считает размеры и доверяет внешним данным, этот класс будет всплывать снова и снова. Просто в новых местах. И отдельно - про цену цели. Одни серверы ломают ради самих серверов, а CI/CD-узел ломают ради всего, что за ним, т.е. исходников, секретов, продакшена, клиентов. Поэтому билд-инфраструктуру стоит защищать особенно внимательно, а не как внутренний сервис, до которого снаружи не дотянутся. Как видим, дотягиваются - одной регуляркой.

Таймлайн и источники​

Таймлайн CVE-2026-97229: раскрытие через HackerOne, патч 12 октября 2026, резонанс в СМИ


Кто нашёл: Johan Carlsson (joaxcar), через bug bounty GitLab на HackerOne. Точная дата репорта нигде не публиковалась. Разбор механики и условий - у Rapid7, обещан отдельный технический анализ.

Первоисточник и материалы:
Дисклеймер: материал носит учебный характер и описывает класс уязвимости и методы защиты. Операционные детали эксплуатации намеренно опущены. Обновляйте GitLab и не держите лишний публичный доступ!
Полезно

Комментарии

0