Статья GitHub Enterprise RCE CVE-2026-3854: от реверса закрытых бинарников до полной компрометации сервера

Три монитора на тёмном рабочем столе в 3 ночи: дизассемблер, сравнение бинарников с красными строками и терминал с сообщением об ошибке сегментации.


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 - полная компрометация конфиденциальности, целостности и доступности.

Механизм инъекции​

Вот что происходит по шагам:
  1. Пользователь выполняет git push -o "value;injected_field=evil" - push option с semicolon внутри значения.
  2. babeld берёт значение push option и встраивает его в X-Stat header без санитизации - semicolon не экранируется.
  3. Поскольку ; - делимитер X-Stat, значение «разламывает» своё поле и создаёт новые поля, полностью контролируемые атакующим.
  4. Благодаря last-write-wins семантике инжектированное поле перезаписывает легитимное, если ключ совпадает.
Wiz подтвердили инъекцию двумя независимыми методами: через binary analysis декомпилированных бинарников babeld/gitrpcd и на wire-level - packet capture на живом GHES-инстансе показал инжектированные поля рядом с легитимными, перезаписывающие их значения.

Концептуально crafted push option выглядит так:
Bash:
# Пример для демонстрации концепции - точный формат X-Stat восстановлен Wiz через RE
git push -o "anything;rails_env=test;custom_hooks_dir=/tmp/evil" origin main
babeld включит это в X-Stat без экранирования. gitrpcd распарсит header, увидит 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.

Эскалация до удалённого выполнения кода​

Инъекция полей - это вход. Дальше вопрос: как из override rails_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-сервисного пользователя с полным доступом к файловой системе
Один строковый параметр. Одна проверка на равенство. Между полной изоляцией и полным доступом. И этот параметр инжектируется.

Полная цепочка​

  1. Инъекция rails_env=test → сервер переключается на non-production path, sandbox отключается
  2. Инъекция custom_hooks_dir → указываем путь к директории, контролируемой атакующим
  3. Инъекция repo_pre_receive_hooks → активируем выполнение custom hooks из указанной директории
Результат: произвольное выполнение shell-команд от имени git-пользователя. На GHES этот пользователь имеет доступ ко всем репозиториям на сервере.

Отдельная находка GitHub при расследовании: non-production code path существовал в container image, хотя был нужен только для другой конфигурации продукта. При переходе на новую deployment-модель exclusion старого кода не перенесли. Sandbox bypass работал потому, что на серверах физически присутствовал код, не предназначенный для этой среды. Классический defence-in-depth failure - мёртвый код убивает живую защиту.

Маппинг по MITRE ATT&CK​

Полная цепочка покрывает несколько тактик:
🔓 Часть контента скрыта: Эксклюзивный контент для зарегистрированных пользователей.

Импакт и масштаб: от одного 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 опубликована
40 минут на подтверждение, два часа на патч - это, к слову, отличная реакция. Видно, что у команды есть отработанный playbook для критических Bug Bounty репортов.

Почему GitHub был уверен: эксплуатации не было​

Свойство уязвимости, которое спасло форензику: эксплойт вынуждает сервер пройти по code path, который не используется при нормальных операциях на github.com. Это неизбежное следствие механизма инъекции - переключение на non-production путь через rails_env невозможно скрыть или подавить. Атакующий не может эксплуатировать баг, не оставляя этот след.

GitHub залогировал аномальный path и запросил телеметрию. Результат:
  • Каждое срабатывание non-production code path соответствовало тестовой активности исследователей Wiz
  • Других аккаунтов, триггеривших этот путь, не обнаружено
  • Данные клиентов не были затронуты
Повезло: атакующий не мог эксплуатировать тихо. Не каждая RCE такая удобная для форензики.

Индикаторы компрометации для 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-приложений - первым делом ищите internal protocols между сервисами и проверяйте, как user input попадает в эти протоколы. Если delimiter протокола может встречаться в user input - injection вероятен.

Я провёл достаточно времени с 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.
 
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab