4 марта 2026 года Wiz Research отправили репорт в GitHub Bug Bounty. Через 40 минут уязвимость подтвердили, через два часа на github.com выкатили патч. Один
git push с crafted push option - и RCE на бэкенд-серверах, доступ к миллионам публичных и приватных репозиториев чужих организаций. Выплата - одна из крупнейших за всю историю программы. На момент раскрытия, по данным Wiz, 88% self-hosted GHES-инстансов оставались уязвимы. Но меня тут цепляет не сама уязвимость - её описали и Wiz, и GitHub. Интересна методология: как AI-augmented реверс-инжиниринг закрытых бинарников вскрыл command injection в многосервисной архитектуре, которую годами не могли отаудитить руками.Архитектура git push внутри GitHub Enterprise Server
Без понимания внутреннего pipeline разговор о CVE-2026-3854 будет абстрактным. Когда пользователь делаетgit push на GitHub через SSH, запрос проходит цепочку из четырёх сервисов - каждый написан на своём языке, каждый безусловно доверяет предыдущему. Вот тут и зарыта собака.babeld - git-прокси и точка входа для всех git-операций. Принимает SSH-соединение, передаёт аутентификацию в gitauth. Фронтовой gateway, который транслирует внешний git-протокол во внутренний.
gitauth - сервис аутентификации. Верифицирует credentials, проверяет push-доступ к целевому репозиторию, возвращает security-политики сессии: лимиты на размер файлов, правила именования веток, LFS-конфигурацию. babeld забирает этот ответ и на его основе конструирует внутренний заголовок X-Stat - центральный носитель security-метаданных.
gitrpcd - внутренний RPC-сервер. Получает запрос от babeld, парсит X-Stat header, настраивает окружение для downstream-процессов. Критический момент: gitrpcd не проводит собственной аутентификации - он полностью доверяет babeld и трактует каждое поле X-Stat как истину в последней инстанции.
pre-receive hook - скомпилированный Go-бинарник, последний рубеж перед принятием push. Проверяет лимиты файлов, правила веток, LFS-целостность, выполняет admin-defined custom hooks.
Формат X-Stat: корень проблемы
X-Stat header - связующее звено между всеми четырьмя сервисами. Он переносит security-критичные поля в формате semicolon-delimited key=value пар. Внутренние сервисы парсят его тривиально: split по;, заполнение map. И здесь деталь, на которой всё держится: map использует last-write-wins семантику. Если ключ встречается дважды - второе значение тихо перезаписывает первое. Без предупреждений, без логов.Когда babeld пересылает push-запрос, в X-Stat включаются push options - произвольные строки, которые пользователь передаёт через
git push -o <value>. Стандартная фича git-протокола для server-side hints. babeld кодирует их как нумерованные поля: push_option_0, push_option_1, плюс push_option_count.Контекст применимости: эта архитектура специфична для GHES (self-hosted) и github.com. Уязвимость затрагивала оба варианта, но импакт различался - на github.com RCE на shared storage nodes, на GHES - полная компрометация сервера.
Методология Wiz Research: AI-augmented реверс-инжиниринг
Для vulnerability researcher именно здесь начинается самое мясо. Wiz описали, что уже копали GHES ранее - Round 1 - но объём скомпилированных closed-source бинарников делал полноценный аудит pipeline нерентабельным. Десятки бинарников без исходников, каждый - отдельный сервис с собственной логикой парсинга. Ручной реверс каждого в IDA Pro занял бы месяцы.Round 2 стал возможен благодаря IDA MCP - AI-augmented тулингу для автоматизации reverse engineering. По заявлению Wiz, это один из первых случаев обнаружения критической уязвимости в closed-source бинарниках при помощи AI.
Пошаговый подход
Извлечение бинарников. GHES поставляется как VM-образ. Внутри - babeld, gitrpcd, pre-receive hook и другие компоненты как скомпилированные бинарники. Первый этап - развернуть VM, вытащить бинарники, подготовить их к анализу в IDA Pro.Автоматизированная декомпиляция. Вместо ручного разбора каждой функции AI-тулинг восстанавливал типы, идентифицировал функции, реконструировал внутренние протоколы. Рутина, которая раньше занимала недели на один бинарник, ускорилась на порядок.
Реконструкция протокола X-Stat. Через декомпиляцию babeld и gitrpcd удалось восстановить формат header'а, семантику полей, логику парсинга (split по
;, last-write-wins). Это знание - ключ к пониманию, что injection возможен.Маппинг потока данных. От SSH-сессии пользователя через babeld в X-Stat header, из X-Stat в gitrpcd и pre-receive hook. Цель - найти точки, где user-controlled input попадает в security-критичный контекст без санитизации. AI-тулинг помог систематически пройти этот путь через все сервисы, а не ковырять иголку в стоге вручную.
Что AI не делал
AI ускорил рутину - декомпиляцию, восстановление типов, идентификацию функций. Но обнаружение уязвимости потребовало человеческого понимания архитектуры. Осознание, что semicolon в shared header format - точка инъекции, что last-write-wins превращает field injection в override security-полей, чтоrails_env управляет sandbox/no-sandbox путём - это domain expertise. AI такое сам не вытянет - ему не хватает контекста «а что будет, если этот парсер встретит два одинаковых ключа».Ограничения подхода
Метод работает для продуктов, поставляемых как VM-образы или контейнеры (GHES, GitLab Omnibus, Bitbucket Server, Jenkins). Для SaaS-only продуктов без self-hosted варианта - вектор закрыт. Для практикующих исследователей: аналогичный подход реализуем через Ghidra + Python-скрипты, хотя AI-интеграция в Ghidra пока менее зрелая, чем IDA MCP.GHES уязвимость RCE: инъекция полей через push options
Ядро CVE-2026-3854 - command injection через недостаточную санитизацию push options. CWE-77: Improper Neutralization of Special Elements used in a Command. CVSS 4.0: 8.7 (HIGH), вектор AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N.Разберём компоненты вектора: PR:L - нужен аутентифицированный пользователь с push-доступом (low privileges, не admin). AC:L - сложность атаки низкая, специальных условий не требуется. UI:N - жертва не должна ничего делать. Тройное H на C/I/A - полная компрометация конфиденциальности, целостности и доступности.
Механизм инъекции
Вот что происходит по шагам:- Пользователь выполняет
git push -o "value;injected_field=evil"- push option с semicolon внутри значения. - babeld берёт значение push option и встраивает его в X-Stat header без санитизации - semicolon не экранируется.
- Поскольку
;- делимитер X-Stat, значение «разламывает» своё поле и создаёт новые поля, полностью контролируемые атакующим. - Благодаря last-write-wins семантике инжектированное поле перезаписывает легитимное, если ключ совпадает.
Концептуально crafted push option выглядит так:
Bash:
# Пример для демонстрации концепции - точный формат X-Stat восстановлен Wiz через RE
git push -o "anything;rails_env=test;custom_hooks_dir=/tmp/evil" origin main
rails_env=test после легитимного rails_env=production - и last-write-wins перезапишет значение. Красота (для атакующего).Security-relevant инжектируемые поля
Через reverse engineering pre-receive binary и wire-level анализ Wiz определили поля X-Stat, которые можно инжектировать:| Поле | Назначение | Импакт при override |
|---|---|---|
| rails_env | Среда выполнения (production/другое) | Обход sandbox |
| custom_hooks_dir | Путь к custom hooks | Контроль выполняемых скриптов |
| repo_pre_receive_hooks | Конфигурация pre-receive hooks | Активация выполнения hooks |
| large_blob_rejection_enabled | Лимит на размер blob'ов | Обход ограничений загрузки |
Первые три - ключ к полной цепочке RCE.
Эскалация до удалённого выполнения кода
Инъекция полей - это вход. Дальше вопрос: как из overriderails_env получить выполнение произвольного кода?GHES поддерживает admin-defined custom pre-receive hooks - скрипты, которые сервер выполняет перед принятием push. Через реверс-инжиниринг pre-receive binary Wiz обнаружили два пути исполнения, которые управляются единственным полем -
rails_env из X-Stat:- rails_env=production → hooks выполняются внутри sandbox'а, с изоляцией файловой системы и ограничениями
- rails_env=что угодно кроме production → hooks выполняются напрямую, без sandbox'а, без изоляции, от имени git-сервисного пользователя с полным доступом к файловой системе
Полная цепочка
- Инъекция
rails_env=test→ сервер переключается на non-production path, sandbox отключается - Инъекция
custom_hooks_dir→ указываем путь к директории, контролируемой атакующим - Инъекция
repo_pre_receive_hooks→ активируем выполнение custom hooks из указанной директории
Отдельная находка GitHub при расследовании: non-production code path существовал в container image, хотя был нужен только для другой конфигурации продукта. При переходе на новую deployment-модель exclusion старого кода не перенесли. Sandbox bypass работал потому, что на серверах физически присутствовал код, не предназначенный для этой среды. Классический defence-in-depth failure - мёртвый код убивает живую защиту.
Маппинг по MITRE ATT&CK
Полная цепочка покрывает несколько тактик:
🔓 Часть контента скрыта: Эксклюзивный контент для зарегистрированных пользователей.
| Этап атаки | Техника ATT&CK | ID | Применение |
|---|---|---|---|
| Разведка и подготовка | Exploits (T1587.004) | Resource Development | Разработка crafted push option через RE бинарников |
| Разведка и подготовка | Vulnerabilities (T1588.006) | Resource Development | Идентификация injection-точки в X-Stat |
| Начальный доступ | Exploit Public-Facing Application (T1190) | Initial Access | git push через SSH-эндпоинт |
| Выполнение | Unix Shell (T1059.004) | Execution | Произвольные команды через custom hooks |
| Повышение привилегий | Exploitation for Privilege Escalation (T1068) | Privilege Escalation | Обход sandbox через rails_env override |
| Уклонение | Deobfuscate/Decode Files or Information (T1140) | Defense Evasion | Манипуляция internal metadata |
| Обнаружение | File and Directory Discovery (T1083) | Discovery | Доступ к файловой системе с чужими репозиториями |
| Закрепление | Web Shell (T1505.003) | Persistence | Потенциальное закрепление через custom hooks |
Контекст для внешнего пентеста: атакующему нужен аутентифицированный аккаунт с push-доступом к хотя бы одному репозиторию. На github.com это любой зарегистрированный пользователь - создал свой репозиторий и погнал. На GHES - зависит от конфигурации, но обычно каждый внутренний пользователь имеет push-доступ.
Контекст для внутреннего пентеста: если в scope есть GHES-инстанс и получены credentials любого пользователя (даже через password spraying или credential stuffing) - это прямой путь к полной компрометации сервера, доступу ко всем репозиториям и внутренним секретам.
Импакт и масштаб: от одного push до миллионов репозиториев
GitHub.com
На github.com уязвимость давала RCE на shared storage nodes. Wiz подтвердили: миллионы публичных и приватных репозиториев других пользователей и организаций были доступны на затронутых нодах. Исследователи действовали ответственно - кросс-тенантный доступ проверили через собственные тестовые аккаунты, подтвердив, что permissions git-пользователя на файловой системе позволяют читать любой репозиторий на ноде. К содержимому чужих репозиториев не обращались.GitHub Enterprise Server
На GHES импакт тяжелее: полная компрометация сервера, включая все hosted-репозитории и внутренние секреты (tokens, SSH-ключи, конфигурации). Для организаций в регулируемых отраслях - финансы, здравоохранение, госсектор - которые выбирают self-hosted именно ради контроля над данными, это катастрофический сценарий: один скомпрометированный пользовательский аккаунт = утечка всей кодовой базы компании.88% уязвимых инстансов
По данным Wiz, на момент публичного раскрытия (28 апреля 2026) 88% GHES-инстансов оставались уязвимыми. Учитывая, что GHES популярен именно в enterprise-сегменте с медленными процессами обновления и change management - цифра предсказуемая, но от этого не менее страшная.Затронутые версии GitHub Enterprise Server - все до:
- 3.14.25, 3.15.20, 3.16.16, 3.17.13, 3.18.7, 3.19.4
Детекция и форензика
Таймлайн реагирования GitHub
По данным блога GitHub (Alexis Wales, CISO):- 4 марта 2026, ~17:05 UTC - получен Bug Bounty репорт от Wiz
- 4 марта 2026, ~17:45 UTC - воспроизведение и подтверждение (40 минут)
- 4 марта 2026, 19:00 UTC - fix deployed на github.com (менее двух часов от репорта)
- Далее - форензика, подготовка патчей для всех поддерживаемых версий GHES
- 28 апреля 2026 - публичное раскрытие, CVE опубликована
Почему GitHub был уверен: эксплуатации не было
Свойство уязвимости, которое спасло форензику: эксплойт вынуждает сервер пройти по code path, который не используется при нормальных операциях на github.com. Это неизбежное следствие механизма инъекции - переключение на non-production путь черезrails_env невозможно скрыть или подавить. Атакующий не может эксплуатировать баг, не оставляя этот след.GitHub залогировал аномальный path и запросил телеметрию. Результат:
- Каждое срабатывание non-production code path соответствовало тестовой активности исследователей Wiz
- Других аккаунтов, триггеривших этот путь, не обнаружено
- Данные клиентов не были затронуты
Индикаторы компрометации для GHES
Для администраторов GHES: GitHub рекомендует проверить/var/log/github-audit.log на наличие push-операций, содержащих символ ; в push options. Это прямой индикатор попытки эксплуатации - легитимные push options не содержат semicolons.
Bash:
# Проверка audit-лога на индикаторы инъекции через push options
grep -E 'push.*option.*\;' /var/log/github-audit.log
Reverse engineering bug bounty: patch analysis и n-day research
Что изменилось в патче
GitHub описал фикс лаконично: санитизация user-supplied push option values, чтобы они не могли влиять на internal metadata fields. Плюс удаление non-production code path из production container images как defence-in-depth мера.Если реконструировать: primary fix - в babeld, где добавлена sanitization-функция для push option values (экранирование или strip символа
;). Secondary - чистка container image от кода, не предназначенного для production.Как использовать патч для обучения
Для тех, кто практикует patch diffing: между GHES 3.19.3 и 3.19.4 изменения в бинарниках babeld и pre-receive hook будут минимальными и точечными. Вытаскиваете оба варианта бинарников, прогоняете черезbindiff или diaphora - и видите добавленные проверки: sanitization для push options в babeld, удалённые code paths в container image. Минимальный diff в огромной кодовой базе - чёткий указатель на root cause.Этот принцип работает и в обратную сторону: видите в патче GHES добавление sanitization для
; в компоненте, обрабатывающем push options, - это прямой указатель на injection-вектор. Именно так patch diffing используется для n-day research. Патч буквально рассказывает, где была дыра.Требования к окружению для воспроизведения
- IDA Pro (от v7.6) с MCP для AI-assisted анализа, или Ghidra (бесплатный, с Python-скриптами для автоматизации)
- GHES VM - trial license для развёртывания тестового инстанса
- Изолированная среда - VMware/VirtualBox для VM, Docker для вспомогательных сервисов
- bindiff или diaphora для patch diffing между версиями бинарников
- RAM: минимум 16 ГБ для комфортной работы с IDA/Ghidra на крупных бинарниках GHES
- Два образа GHES: уязвимая версия (до 3.19.4) и пропатченная (3.19.4+)
Паттерн для будущих исследований
CVE-2026-3854 - учебный экземпляр класса уязвимостей, возникающих на trust boundaries в многосервисной архитектуре. babeld доверяет user input, gitrpcd доверяет babeld, pre-receive hook доверяет gitrpcd. Ни один из них не валидирует данные от предыдущего звена. Delimiter injection - прямое следствие несогласованности: один сервис вставляет данные в протокол, другой парсит, но формат экранирования не определён.Этот паттерн регулярно встречается в enterprise-продуктах:
- Internal HTTP headers (X-Forwarded-For injection, X-Original-URL injection)
- gRPC metadata между микросервисами
- Кастомные бинарные протоколы с delimiter-based форматами
- Environment variables, передаваемые между процессами в pipeline
Я провёл достаточно времени с enterprise git-серверами в IDA, чтобы утверждать: CVE-2026-3854 - не аномалия, а закономерность. Многосервисные архитектуры с internal delimiter-based протоколами плодят injection-баги на trust boundaries с предсказуемой регулярностью. Проблема в том, что их почти никто не ищет - closed-source бинарники пугают объёмом, и большинство исследователей обходят их стороной, предпочитая web-фронтенды и открытые API.
Wiz показали, что AI-augmented RE меняет экономику: аудит, который раньше был нерентабельным, стал окупаться. Но IDA MCP - мультипликатор, не замена. Инструмент не нашёл бы этот баг без человека, который понимает, что semicolon в shared header - точка инъекции, и что last-write-wins парсинг превращает field injection в override security-полей.
Через год-два количество исследователей с таким стеком (IDA MCP, bindiff, понимание multi-service protocol analysis) вырастет кратно. Enterprise-продукты, десятилетиями прятавшиеся за «closed-source = secure», получат волну критических CVE. Кто осваивает эту методологию сейчас - будет собирать bounty, пока остальные разбираются, что такое X-Stat header.