Статья CVE-2026-45131 + CVE-2026-45132: уязвимость GitHub Actions — fork PR крадёт секреты CI/CD

Крупный план платы CI-раннера с вскрытым экраном, обгоревшим чипом и разветвлённой трассой к JTAG-порту. Надпись на плате: «CVE-2026-45131 · SECRETS EXFIL», оборванный кабель с меткой «DOCKERPASSWO...


Два CVE с CVSS 10.0 - потолок шкалы NVD. Ноль привилегий у атакующего, ноль взаимодействия со стороны maintainer'а. На выходе - Docker Hub credentials, Personal Access Token и SSH-ключ подписи коммитов. Всё, что понадобилось: анонимный GitHub-аккаунт, форк публичного репозитория CloudPirates Open Source Helm Charts и один pull request.

Обе уязвимости закрыты коммитом fcf9302. Но паттерн, который их породил, живёт в тысячах репозиториев. И находится за 30 секунд через GitHub Code Search - я проверял.

Бизнес-логика атаки: зачем атакующему CI/CD-пайплайн Helm Charts​

[Применимо: внешний пентест / bug bounty, публичные репозитории GitHub с CI/CD-пайплайнами] Подробнее - в нашем обзоре атаки на цепочку поставок.

CloudPirates Open Source Helm Charts - коллекция чартов для Kubernetes-деплоя. Казалось бы, ну чарты и чарты. Но компрометация CI/CD этого репозитория давала атакующему три направления:

Перехваченные Docker Hub credentials позволяют выполнить docker login, собрать образ с бэкдором и запушить его под легитимным тегом. Каждый helm install или docker pull у downstream-пользователей доставляет вредоносный код. По MITRE ATT&CK - Malicious Image (T1204.003, Execution).

SSH-ключ подписи и PAT дают возможность коммитить в основную ветку от имени maintainer'а. Изменённые Helm-шаблоны могут встроить вредоносные initContainers в деплойменты - Compromise Software Dependencies and Development Tools (T1195.001, Initial Access). GitHub покажет зелёный бейдж Verified, и никто не почешется.

Personal Access Token GitHub обычно имеет доступ к нескольким репозиториям организации. Один PR - и атакующий получает плацдарм для перемещения по всей DevOps-инфраструктуре: Steal Application Access Token (T1528, Credential Access).

Это не теория. По данным Orca Security, исследователи скомпрометировали репозитории Microsoft, Google и NVIDIA - каждый раз через единственный fork pull request. Sysdig TRT нашёл десятки уязвимых workflow в проектах MITRE ATT&CK CAR и Splunk Security Content. А CVE-2025-30066 (tj-actions/changed-files, CVSS 8.6, CWE-506) попал в CISA KEV с подтверждённой эксплуатацией в дикой природе - затронуто свыше 23 000 репозиториев.

pull_request vs pull_request_target: корень уязвимости секретов CI/CD​

Вся проблема - в том, как GitHub Actions обрабатывает два похожих триггера. Разница между ними не нюанс конфигурации, а граница безопасности. И эту границу пересекают с завидной регулярностью.

Характеристикаpull_requestpull_request_target
Контекст выполненияmerge commit (код PR)base branch (основная ветка)
Доступ к секретам репозиторияНет (для fork PR)Да
Права GITHUB_TOKENread-onlyread/write по умолчанию
Запуск от fork'овТребует approvalЗапускается автоматически
Целевое назначениеТестирование кода PRАвтоматизация с привилегиями

Триггер pull_request_target задумывался для задач, не требующих выполнения кода из PR: проставить лейбл, оставить комментарий, обновить статус чека. Его привилегированный контекст - фича, а не баг. Проблема возникает, когда workflow с pull_request_target делает actions/checkout с ссылкой на head.ref или head.sha из PR - тут-то и начинается: код атакующего выполняется в привилегированном окружении.

GitHub предупредил об этом ещё в августе 2021 года. Толку мало. По данным GitHub Security Lab, разработчики регулярно совершают одну из трёх ошибок:

Прямой checkout PR-кода с параметром ref: ${{ github.event.pull_request.head.ref }} - классический путь к code injection. Workflow скачивает код из форка, но выполняет его с секретами основного репозитория. Красота.

TOCTOU-атака (Time of Check, Time of Use) - PR проходит ревью, получает одобрение, после чего атакующий дописывает вредоносный коммит до запуска workflow. Ссылка на head.ref (ветку) мутабельна - атакующий может в любой момент обновить её содержимое. Ссылка на конкретный head.sha (коммит) иммутабельна, но и она не спасает, если workflow запускается автоматически при synchronize.

Отравление кэша - даже workflow с permissions: {} может записать вредоносные данные в Actions cache. Другие, привилегированные workflow, которые используют тот же кэш, выполнят подменённый код. Самый подлый вариант - не оставляет следов в конфигурации привилегированного workflow.

CVE-2026-45131 и CVE-2026-45132: два workflow, один паттерн​

1783629942636.webp

Оба CVE затрагивают CloudPirates Open Source Helm Charts. Оба получили CVSS 10.0 (CRITICAL), оба классифицированы как CWE-94 (Improper Control of Generation of Code), оба исправлены одним коммитом fcf9302. Но компрометируют разные workflow с разными последствиями.

pull-request.yaml: Docker Hub credentials без одобрения maintainer'а​

CVE-2026-45131 - workflow pull-request.yaml выполнял код из fork pull request в привилегированном контексте, раскрывая repository secrets: Docker Hub credentials и токены. Одобрение maintainer'а не требовалось.

Вектор CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N. Разберём по компонентам:
  • AV:N - атака через сеть (веб-интерфейс GitHub или API)
  • AC:L - никаких специальных условий, достаточно создать PR
  • PR:N - анонимный GitHub-аккаунт, ноль привилегий в целевом репозитории
  • UI:N - maintainer не должен ничего нажимать
  • S:C (Changed scope) - компрометация выходит за границы репозитория: Docker Hub, downstream-пользователи образов
  • C:H / I:H - полный доступ к секретам и возможность модификации (push образов, изменение кода)
CISA SSVC оценивает Technical Impact как total, Exploitation как none (на дату анализа активной эксплуатации не зафиксировано), но Automatable: yes - атака полностью автоматизируема скриптом.

generate-schema.yaml: PAT и SSH-ключ подписи коммитов​

CVE-2026-45132 затрагивает другой workflow - generate-schema.yaml, который раскрывал Personal Access Token и SSH signing key для fork-контролируемого кода из-за небезопасного checkout'а и обработки credentials.

CVSS-вектор идентичен: 10.0, тот же CWE-94. Но последствия шире. SSH signing key позволяет подписывать коммиты от имени maintainer'а - GitHub покажет зелёный бейдж Verified. А PAT обычно имеет более широкий scope, чем GITHUB_TOKEN, включая доступ к приватным репозиториям организации. Если организация не ограничивала scope PAT по принципу минимальных привилегий (а кто ограничивает?) - один скомпрометированный токен открывает доступ ко всей кодовой базе.

Полная цепочка атаки: от форка до supply chain compromise​

1783630000967.webp

[Применимо: внешний пентест / bug bounty против публичных GitHub-репозиториев с CI/CD. Не применимо к приватным репозиториям без fork-доступа.]

Требования к окружению для воспроизведения: GitHub-аккаунт (бесплатный), браузер или gh CLI (v2.x+), для локального анализа workflow - act (локальный раннер GitHub Actions, активный open-source проект). ОС: любая с доступом к GitHub. Для поиска уязвимых репозиториев - GitHub Code Search (встроенный, не требует ничего дополнительного).

Шаг 1. Разведка - Code Repositories (T1213.003, Collection). Атакующий находит публичный репозиторий с pull_request_target в .github/workflows/. Поиск тривиален: GitHub Code Search по запросу pull_request_target path:.github/workflows filename:*.yml в комбинации с actions/checkout и head.ref. Sysdig TRT использовал именно этот подход и нашёл уязвимые workflow в репозиториях MITRE, Splunk и десятках других проектов.

Шаг 2. Подготовка payload - Poisoned Pipeline Execution (T1677, Execution). Атакующий форкает репозиторий и модифицирует файл, который workflow исполняет. Вариантов несколько: подмена shell-скрипта из run: блока, модификация setup.py (при pip install . выполняется произвольный Python-код), изменение Makefile или package.json. В случае CVE-2025-47928 (spotipy, CVSS 9.1, CWE-488) исследователи Sysdig TRT модифицировали setup.py, добавив кастомный install-хук, который дампил память процесса runner worker через публично доступный скрипт memdump.py и отправлял секреты на внешний S3-бакет. Открытие PR на основной репозиторий триггерит workflow автоматически - Unix Shell (T1059.004, Execution).

Шаг 3. Эксфильтрация секретов - Credentials In Files (T1552.001, Credential Access). Payload обращается к переменным окружения: $DOCKER_PASSWORD, $DOCKER_USER, $GITHUB_TOKEN, или дампит память runner worker. Секреты отправляются на контролируемый сервер - Exfiltration Over Webhook (T1567.004, Exfiltration). Для CVE-2025-47928 были успешно извлечены SPOTIPY_CLIENT_ID, SPOTIPY_CLIENT_SECRET и GITHUB_TOKEN с дефолтными write-правами на весь репозиторий.

Шаг 4. Post-exploitation. С Docker Hub credentials атакующий пушит образ с бэкдором под легитимным тегом. С PAT - коммитит в приватные репозитории организации. С SSH signing key - подписывает коммиты с verified-бейджем. Всё чисто, всё "легитимно".

Уязвимый паттерн, общий для обоих CVE и десятков аналогичных находок:
YAML:
on:
  pull_request_target:
    types: [opened, synchronize]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.ref }}
          repository: ${{ github.event.pull_request.head.repo.full_name }}
      - run: ./scripts/build.sh
ref и repository указывают на форк атакующего. После checkout'а ./scripts/build.sh - уже не оригинальный скрипт, а заменённый атакующим. Но выполняется он с секретами основного репозитория. Вот и весь фокус.

Ограничения техники. Require approval for all outside collaborators (Settings -> Actions -> Fork pull request workflows) замедляет атаку, но не блокирует - TOCTOU: PR одобряют, атакующий дописывает вредоносный коммит, workflow берёт обновлённый head.ref. Branch protection rules на pull_request_target не влияют - workflow запускается до merge. Приватные репозитории без fork-доступа к этому вектору неуязвимы. Организации с OIDC-аутентификацией вместо long-lived Docker Hub credentials снижают импакт: скомпрометированный OIDC-токен ограничен scope'ом и временем жизни.

Чеклист аудита GitHub Actions workflow для пентестера

Готовый список для передачи в отчёт или проверки на bug bounty. Копируйте как есть.

Поиск уязвимых workflow:
  1. Клонируйте целевой репозиторий, выполните grep -r "pull_request_target" .github/workflows/
  2. Для каждого найденного файла проверьте actions/checkout с ref:, указывающим на head.ref, head.sha или merge ref PR
  3. Проверьте, выполняет ли workflow код из чекаута: run: блоки, pip install ., npm install, make, вызовы скриптов из репозитория
  4. Проверьте блок permissions: - его отсутствие означает дефолтные read/write права для GITHUB_TOKEN
  5. Ищите secrets.* в env: или with: блоках - они доступны исполняемому коду
  6. Проверьте workflow_run триггеры, запускающиеся после pull_request - потенциальный privilege escalation: непривилегированный workflow триггерит привилегированный
Ремедиация:
  1. Замените pull_request_target на pull_request везде, где workflow не требует доступа к секретам
  2. Если pull_request_target необходим - добавьте проверку источника: if: github.event.pull_request.head.repo.full_name == github.repository - это отсекает fork PR
  3. Используйте workflow splitting: непривилегированный pull_request выполняет тесты, привилегированный workflow_run обрабатывает результаты, но с жёсткой валидацией артефактов на границе
  4. Задайте минимальные permissions в каждом workflow явно - contents: read вместо дефолтных
  5. Переведите Docker Hub и облачных провайдеров на OIDC вместо long-lived credentials в секретах
  6. На уровне организации: Settings -> Actions -> Fork pull request workflows -> Require approval for all outside collaborators
  7. Автоматизируйте аудит: actionlint (статический анализ workflow, активный проект) и semgrep с правилами для YAML-конфигов Actions выявляют описанные паттерны до merge

EPSS vs CVSS: приоритизация уязвимостей CI/CD на пентесте

📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме

Паттерн не меняется: checkout PR-кода в привилегированном контексте. Проблема не в сложности атаки - она тривиальна для любого, кто прочитал полстраницы документации GitHub. Проблема в дефолтном поведении: pull_request_target из коробки имеет write-доступ к GITHUB_TOKEN и полный доступ к секретам. Пока дефолт не станет secure-by-default - read-only, без секретов, с обязательным approval - каждый новый публичный репозиторий с CI/CD-пайплайном это потенциальный CVE 10.0.

Аргумент "у нас маленький проект, нас никто не атакует" разбивается об автоматизацию: GhostAction скомпрометировал 817 репозиториев одним скриптом, не выбирая по размеру. На каждом внешнем пентесте или bug bounty, где в scope есть GitHub-организация, первое, что я проверяю - .github/workflows/*.yml на pull_request_target с unsafe checkout. Это 30 секунд работы. И с ненулевой вероятностью - critical finding.

Пока GitHub не изменит дефолт, этот вектор останется самым дешёвым entry point в supply chain. Попробуйте прогнать свои репозитории через grep -r "pull_request_target" .github/workflows/ прямо сейчас - результат может удивить. На WAPT эту цепочку разбирают в модуле по CI/CD с полноценной лабой.
 
Последнее редактирование модератором:
Мы в соцсетях:

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

Похожие темы

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🧭 Навигатор · ИБ 2026
Не знаешь, какой трек твой?
5 направлений ИБ, реальные зарплаты и точка входа для каждого — в одном треде.
JuniorSenior+
100K → 600K+ ₽ /мес
Открыть навигатор →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab