РАЗБОР
Статья
Разбор CVE-2026-97229 или как .gitlab-ci.yml роняет сервер
[ обложка статьи ]
Режим чтения
Что вас ждёт в статье:
- Краткая сводка
- 1. Откуда в CI/CD вообще регулярки
- 2. Как число ломает всё: CWE-190
- 3. «Unauth» со звёздочкой
- 4. Почему это критично
- 5. Как это ловить
- 6. Защита и патч
- Заключение
- Таймлайн и источники
Краткая сводка
Десятка по 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) |
| CVSS | 10.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 |
1. Откуда в CI/CD вообще регулярки
Давайте начнём с азов - что такое CI/CD и зачем оно. Наверняка, не раз душили этим вопросом на собесе, так что своими словами. Это конвейер, который сам собирает, проверяет и выкатывает твой код. CI - непрерывная интеграция, т.е. закоммитил, а система сама прогнала сборку и тесты, не дожидаясь, пока кто-то про них вспомнит. CD - непрерывная доставка: прошло проверки - поехало на сервер, часто вообще без ручных действий. Смысл - убрать человека из рутины между написанием кода и кручения кода в проде. И вся эта машинерия описывается одним файлом в корне репозитория - .gitlab-ci.yml. GitLab читает его и понимает, какие джобы, в каком порядке и при каких условиях запускать. Удобно это ровно до того момента, пока тот же файл не становится точкой входа для атакующего. Если программист хоть раз настраивал пайплайн, то писал и сам регулярки, хотя-бы когда запускал джобу только для релизных веток, когда ловил теги определённого вида или отфильтровывал пути файлов. Всё это - регулярные выражения, которые живут в .gitlab-ci.yml, в правилах вроде rules:if и им подобных, и которые GitLab честно пытается разобрать.Суть в том, что регулярка в конфиге - это данные, которыми управляет тот, кто запушил коммит, а не настройка от доверенного админа. Любой ввод, попадающий в парсер, надо проверять - и GitLab, казалось бы, проверяет. Проблема в том, что проверяет он не совсем то, что потом исполняет, но об этом в следующем разделе.
2. Как число ломает всё: CWE-190
Теперь самое интересное - почему безобидная на вид регулярка роняет сервер. И тут не всё так банально, как посчитали размер с переполнением и записали за буфер. Механика тоньше, и красота именно в ней.Сначала GitLab компилирует регулярку в компактной US-ASCII-форме и проверяет её - валидация проходит, всё чисто. А дальше в игру вступает Onigmo, движок регулярок в Ruby, и перекомпилирует выражение уже под 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-ci.yml и странные пути. Пуши .gitlab-ci.yml от свежих аккаунтов, особенно в проекты, где в пути репозитория попадаются не-ASCII-символы, - прямой отголосок условий эксплуатации.
Дочерние процессы сервера. Процесс GitLab от пользователя git, внезапно порождающий оболочку или сетевые утилиты, - один из самых честных сигналов RCE. Телеметрия запуска процессов тут главный друг.
Новые сущности с правами. Внезапно появившиеся пользователи, токены, deploy-keys, раннеры, хуки, интеграции. После захвата атакующий закрепляется именно через них.
Трафик и секреты. Необычные исходящие соединения с сервера и обращения к CI/CD-переменным и секретам, которых по логике быть не должно.
Логика такая же, как в любом поведенческом детекте - мы не ищем конкретный payload, ловим цепочку подозрительная доставка → посторонний процесс → новые доступы → отток данных. Готовых WAF-правил от вендора я не встречал - это, честно говоря, скорее мой вывод, чем подтверждённый факт, так что правила придётся собирать самому по своему трафику.
6. Защита и патч
Тут всё коротко: обновляться. Полноценного обходного пути, который чинит сам баг, вендор не даёт - вылечит только патч.- Обновить self-managed GitLab до 19.4.2, 19.3.5 или 19.2.8. Один апдейт закрывает не только эту дыру, но и пять других High-багов из того же релиза (а заодно 14 Medium и 4 Low).
- Брать именно 19.3.5, а не 19.3.4: промежуточный релиз притащил отдельную проблему со страницей merge request - правда, она вылезала только при включённой интеграции Ona.
- Проверить реальную версию запущенного приложения, особенно в контейнерах и Helm: номер чарта и версия GitLab - не одно и то же, легко обмануться.
- ограничить анонимный доступ к публичным проектам;
- запретить недоверенным пользователям создавать и писать в публичные проекты;
- отключить ненужную публичную саморегистрацию.
Не имеет смысла ждать технических деталей, чтобы понять, насколько всё серьёзно, ведь к моменту их полного раскрытия патч давно должен стоять. Десятка по CVSS - сама по себе исчерпывающая мотивация.
Заключение
Мораль даже тут про то, что мы называем вводом. Регулярку в конфиге пайплайна никто не воспринимает как опасные данные от постороннего - это же просто строчка в .yml, часть настройки. А оказалось - полноценный ввод, который доезжает до низкоуровневого кода и ломает ему логику исполнения. Переполнение целого - баг, которому не одно десятилетие. Казалось бы, где современный DevOps-комбайн, а где арифметика на переполнении счётчика. Но пока код считает размеры и доверяет внешним данным, этот класс будет всплывать снова и снова. Просто в новых местах. И отдельно - про цену цели. Одни серверы ломают ради самих серверов, а CI/CD-узел ломают ради всего, что за ним, т.е. исходников, секретов, продакшена, клиентов. Поэтому билд-инфраструктуру стоит защищать особенно внимательно, а не как внутренний сервис, до которого снаружи не дотянутся. Как видим, дотягиваются - одной регуляркой.Таймлайн и источники
Кто нашёл: Johan Carlsson (joaxcar), через bug bounty GitLab на HackerOne. Точная дата репорта нигде не публиковалась. Разбор механики и условий - у Rapid7, обещан отдельный технический анализ.
Первоисточник и материалы:
- Релиз-пост GitLab: docs.gitlab.com/releases/patches/patch-release-gitlab-19-4-2-released/
- Разбор Rapid7 (механика, условия, митигации): rapid7.com/blog/post/etr-cve-2026-97229-critical-unauthenticated-remote-code-execution-in-gitlab/
- Help Net Security: helpnetsecurity.com/2026/10/13/gitlab-ci-cd-regular-expression-compiler-vulnerability-cve-2026-97229/
- CSO Online: csoonline.com/article/4234306/
- The Hacker News: thehackernews.com/2026/10/gitlab-patches-cvss-100-flaw-that-could.html
- eSecurity Planet: esecurityplanet.com/news/news-gitlab-critical-server-takeover-flaw/
AI-выжимка
сгенерировано ИИ
Тезисы статьи скоро
Статья читается полностью. Тезисы со ссылками на разделы появятся позже.
Содержание
Codeby Academy
Практика и мастерство
От основ до продвинутого — программы для практиков Codeby.
Перейти к курсу →
Комментарии
0