Расплата за Pwn-запросы: что все еще работает против GitHub Actions

Источник: wiz.io•

Расплата за Pwn-запросы: что все еще работает против GitHub Actions

Изменения в GitHub Actions 2026 года усложняют атаки типа pull_request_target, но не делают их невозможными. Вот оставшаяся поверхность атак и способы ее аудита.

В течение многих лет pull_request_target был каноническим вектором Pwn-запросов: триггером рабочего процесса (workflow), который выполняется с секретами репозитория, но может быть запущен любым желающим, открывшим пулл-реквест из форка. В 2026 году компания GitHub ответила изменениями платформы, которые существенно усложняют эксплуатацию в сценарии по умолчанию.

Эти изменения представляют собой реальный прогресс. Защита извлечения кода (checkout), токены кэша только для чтения и политики событий закрывают пути, которые делали эксплуатацию тривиальной. Но они не устраняют класс уязвимостей. Они смещают его. Злоумышленники, которые раньше эксплуатировали любой workflow с pull_request_target, теперь должны искать явные разрешения (opt-in), устаревшие версии checkout, незащищенные триггеры или инъекции скриптов.

В Части 1 нашего исследования GitHub Actions мы изложили модель угроз и основные средства защиты, а Часть 2 была посвящена действиям на базе ИИ. Эта публикация обновляет модель угроз для 2026 года: от чего на самом деле защищают изменения GitHub, где остаются пробелы и как защитникам нужно адаптироваться. Чек-лист аудита в конце содержит рекомендации о том, что искать в собственных workflow.

Масштабное обновление безопасности 2026 года: хронология изменений

Признавая системный риск, создаваемый Pwn-запросами, в 2026 году GitHub систематически закрывал бреши посредством поэтапного внедрения средств защиты на уровне платформы и обновлений действий (actions).

Остаточный риск: что все еще работает

Хотя эта последовательность обновлений платформы значительно усиливает конфигурации безопасности по умолчанию для GitHub Actions, она не устраняет все риски.

Применение ограничений 2 декабря — это дефолтное поведение, а не запрет. Мейнтейнеры, чья автоматизация зависит от pull_request_target (метки, превью-деплои, CI форков с секретами), добавят политику событий, разрешающую это. Именно эти workflow работают с недоверенными входными данными при повышенных привилегиях. Политика по умолчанию также не распространяется на частные и внутренние репозитории или на workflow_run. В оставшейся части этого раздела описывается, как такие уцелевшие workflow все еще могут быть скомпрометированы.

Закрепление SHA (SHA Pinning): парадокс безопасности

Один из самых тревожных выводов в нашем исследовании касается того, как стандартный совет по укреплению безопасности цепочки поставок — в частности, закрепление действий (actions) за конкретными хешами коммитов (SHA) — может непреднамеренно обходить средства защиты платформы GitHub.

Поскольку бэкпортированные средства защиты GitHub опираются на логику, встроенную в сам actions/checkout, закрепление за значениями SHA, выпущенными до релиза бэкпорта от 20 июля 2026 года, означает, что workflow запускает устаревший код без каких-либо проверок безопасности. Наше эмпирическое тестирование триггеров workflow подтвердило это расхождение: ссылки на изменяемые теги, такие как @v4 (разрешающиеся в v4.4.x), или закрепленные версии, такие как @v4.4.0, остаются защищенными, в то время как более старые версии, такие как @v4.3.0, или ссылки на SHA старых релизов (например, @08eba0b..., разрешающиеся в v4.3.0), никакой защиты не обеспечивают.

Для поддержания защиты команды безопасности, использующие закрепление SHA, должны убедиться, что их ссылки обновлены до минимальных версий с исправлениями или выше: v2.8.0, v3.7.0, v4.4.0, v5.1.0, v6.1.0 или v7.0.0+.

Пользовательские скрипты checkout и сторонние действия

Второй пробел касается workflow, которые вообще обходят actions/checkout. Защита GitHub реализована внутри кода самого действия — любой альтернативный метод получения кода работает без ограничений. Прямые вызовы CLI полностью обходят защитные механизмы:

Сторонние действия checkout реализуют собственную логику git, поэтому они не наследуют эти средства защиты. Любое из них, настроенное с использованием ссылки на вершину PR (PR head ref), извлечет код форка без ограничений.

Незащищенные привилегированные триггеры

Защита checkout от GitHub распространяется только на pull_request_target и workflow_run:

Pwn-запросы, запускаемые для других типов событий помимо pull_request_target (таких как issue_comment), не будут заблокированы этим изменением. Дальнейшее усиление защиты дополнительных событий может быть рассмотрено в будущих релизах. Журнал изменений GitHub

Pwn-запросы, запускаемые для других типов событий помимо pull_request_target (таких как issue_comment), не будут заблокированы этим изменением. Дальнейшее усиление защиты дополнительных событий может быть рассмотрено в будущих релизах.

Журнал изменений GitHub

Это создает несогласованную границу безопасности: элементы управления кэшем GitHub явно рассматривают issue_comment как триггер с низким уровнем доверия, в то время как защита checkout — нет. Workflow, использующие issue_comment для команд бота (/deploy, /test, /cherry-pick), остаются полностью уязвимыми при использовании checkout v7 — флаг allow-unsafe-pr-checkout не требуется:

Другие привилегированные триггеры имеют тот же пробел. repository_dispatch также выполняется с секретами, и он уязвим, когда workflow использует данные полезной нагрузки (payload), на которые повлиял злоумышленник, для выбора ссылки checkout.

Пока GitHub не расширит защиту checkout, эти workflow должны обеспечивать собственную авторизацию, причем она должна действовать с момента срабатывания события до выполнения кода:

- Проверяйте, кто инициировал событие. Для команд в комментариях проверяйте реальные разрешения комментатора в репозитории (getCollaboratorPermissionLevel, write или выше). Не полагайтесь на author_association, так как она не отражает текущие разрешения. Для repository_dispatch ограничивайте круг лиц, обладающих токеном, способным отправлять событие, и не рассматривайте полезную нагрузку как доказательство того, кто ее отправил.

- Закрепляйте то, что было одобрено. refs/pull/N/merge является изменяемым, поэтому злоумышленник может отправить вредоносный коммит после того, как мейнтейнер прокомментирует /deploy. Извлекайте точный хэш SHA вершины и прерывайте выполнение, если PR был обновлен после публикации комментария.

- Рассматривайте полезную нагрузку события как ненадежные входные данные. Проверяйте аргументы комментариев и поля полезной нагрузки отправки по строгому регулярному выражению белого списка с привязкой и передавайте их в оболочку (shell) через переменные окружения, но никогда через интерполяцию ${{ }}.

Инъекция скриптов: Pwn-запрос без извлечения кода (Checkout)

Распространенное заблуждение заключается в том, что уязвимости pull_request_target требуют извлечения кода форка. На самом деле злоумышленники могут добиться выполнения кода без какого-либо шага checkout посредством инъекции скриптов.

Когда авторы workflow интерполируют контекст события напрямую в команды оболочки с помощью выражений ${{ }}, GitHub подставляет значение в скрипт до запуска оболочки, поэтому данные, контролируемые злоумышленником, становятся частью самой команды:

Злоумышленник просто создает пулл-реквест с вредоносным заголовком:

"; touch /tmp/pwned; echo INJECTED #

Кавычка закрывает строку, точка с запятой завершает echo, а все, что идет после нее, выполняется как новая команда с секретами и токеном задания (job). Ни одно из изменений GitHub 2026 года этого не затрагивает: здесь нет checkout для блокировки, а уязвимость заключается в том, как workflow обрабатывает недоверенные входные данные.

Исправление заключается в том, чтобы никогда не интерполировать недоверенные входные данные в скрипт. Вместо этого передавайте их через переменную окружения, чтобы оболочка воспринимала их как данные:

Двойные кавычки вокруг $PR_TITLE имеют значение. Без них оболочка по-прежнему будет разбивать значение по словам (word-splitting) и раскрывать подстановочные знаки (glob-expanding).

Постоянные каналы данных: за пределами токенов кэша

Токены кэша GitHub только для чтения защищают службу кэширования Actions, но workflow все равно могут обмениваться данными через каналы, которые остаются незатронутыми.

Отравление артефактов (Artifact Poisoning)

Действия upload-artifact и download-artifact работают независимо от токенов кэша. Классическая цепочка атак использует непривилегированный рабочий процесс pull_request для загрузки собранных злоумышленником артефактов, а затем привилегированный workflow_run для их сканирования и выполнения:

Это именно тот сценарий workflow_run, для предотвращения которого создавался checkout v7, однако здесь он не помогает. Привилегированный рабочий процесс никогда не выкачивает (не выполняет checkout) код форка; он его скачивает. В отличие от записей в кэше, на платформе нет эквивалента, который помечал бы артефакты от недоверенных запусков как скомпрометированные, поэтому любой рабочий процесс, который скачивает и выполняет артефакты из менее доверенного запуска, наследует этот риск.

Состояние автономных хостеров (Self-Hosted Runner State)

На неэфеемерных (постоянных) автономных хостерах состояние файловой системы сохраняется между заданиями. Локальные кэши менеджеров пакетов (~/.npm, ~/.cache/pip), каталоги сборки и установленные инструменты находятся на диске хостера, вне службы кэширования Actions, поэтому токены кэша только для чтения на них не распространяются. Код злоумышленника, выполняемый под управлением недоверенного триггера, может записывать данные в эти расположения и отравить следующий доверенный рабочий процесс, запланированный на той же машине, включая задания по релизу и публикации. Используйте эфемерные хостеры, которые очищаются после каждого задания, для любых рабочих процессов, которые могут запускаться недоверенными событиями, и держите недоверенные и доверенные рабочие процессы в отдельных группах хостеров.

Заблуждение, которое никак не умрет

Многие команды безопасности считают, что у них есть еще один рубеж защиты: настройка «Require approval for all external contributors» (Требовать одобрения для всех внешних контрибьюторов) в разделе Settings -> Actions -> General. Если она включена, PR из форков требуют одобрения мейнтейнера до запуска рабочих процессов. Проблема решена, верно?

Не совсем. Эта настройка ограничивает только рабочие процессы pull_request, которые запускают собственный YAML-файл рабочего процесса форка. Для pull_request_target GitHub считает определение рабочего процесса доверенным (оно поступает из базового репозитория), поэтому настройка одобрения не применяется, и запуск начинается немедленно.

Ирония судьбы глубока: pull_request_target — это более опасный триггер (доступ к секретам, токены с правами записи), и при этом именно он обходит требование об одобрении. Если рабочему процессу pull_request_target требуется проверка человеком, поместите привилегированное задание в окружение с обязательными рецензентами или ограничьте список тех, кто может его запустить, с помощью правил Workflow Execution Protections для субъектов.

Явное согласие: allow-unsafe-pr-checkout

Наконец, разработчики, которым необходимо взаимодействовать с кодом форка, могут явно отключить средства защиты checkout, включив флаг allow-unsafe-pr-checkout:

Хотя имя параметра намеренно звучит тревожно, решительно настроенные команды разработки или сторонние рабочие процессы все равно могут использовать его для сохранения устаревшей автоматизации, фактически вновь открывая тот же самый вектор выполнения. Команды безопасности должны уделять особое внимание любому использованию этого флага.

Что изменилось

Подводя итог, развертывание мер безопасности GitHub в 2026 году заслуживает похвалы. Политики событий по умолчанию, средства защиты checkout и токены кэша только для чтения делают тривиальные запросы Pwn (форк, PR, профит) значительно сложнее. Со 2 декабря pull_request_target больше не будет запускаться в публичных репозиториях, если политика событий явно это не разрешает. Отравление кэша через кэш Actions смягчено. Это реальные победы.

Но класс уязвимостей не ликвидирован. Он сместился. Что остается уязвимым: явное согласие через allow-unsafe-pr-checkout, версии с закреплением по SHA, предшествующие бэкпорту июля 2026 года, пользовательские скрипты checkout, обходящие элементы управления на уровне действий, инъекции скриптов, не требующие checkout вообще, незащищенные триггеры вроде issue_comment и repository_dispatch, цепочки отравления артефактов от pull_request к workflow_run, постоянное состояние на неэфеемерных автономных хостерах, а также частные или внутренние репозитории, на которые политика по умолчанию не распространяется. Поверхность атаки уменьшается, но то, что остается, обнаружить сложнее.

Что с этим делать

Начните с аудита того, что вы уже запускаете, а затем опередите вступление в силу требований 2 декабря.

Проведите аудит своих рабочих процессов

Wiz Code автоматически сканирует рабочие процессы GitHub Actions на предмет Pwn-запросов, инъекций скриптов, пробелов в версиях и рискованных конфигураций, чтобы покрыть эту остаточную поверхность атаки. Кроме того, используйте инструменты с открытым исходным кодом, такие как zizmor, или даже следующие простые поисковые запросы grep для начала аудита:

Кроме того, запустите этот скрипт, чтобы разрешить каждый SHA в его тег релиза, а затем сравните с исправленными версиями:

Подготовка к применению правил для pull_request_target со 2 декабря

Со 2 декабря политика событий по умолчанию будет блокировать pull_request_target в публичных репозиториях, у которых нет явной политики событий. workflow_run это не затрагивает, как и частные или внутренние репозитории. До этой даты:

  • Проверьте аналитику политик (policy insights), чтобы узнать, какие запуск pull_request_target были бы заблокированы.

Проверьте аналитику политик (policy insights), чтобы узнать, какие запуск pull_request_target были бы заблокированы.

  • Решите для каждого рабочего процесса, действительно ли ему нужен pull_request_target. Если нет, перенесите его на pull_request. Если привилегированный последующий шаг все же необходим, рабочий процесс workflow_run может справиться с этим, но он должен относиться к артефактам запуска PR как к недоверенным данным и никогда их не выполнять.

Решите для каждого рабочего процесса, действительно ли ему нужен pull_request_target. Если нет, перенесите его на pull_request. Если привилегированный последующий шаг все же необходим, рабочий процесс workflow_run может справиться с этим, но он должен относиться к артефактам запуска PR как к недоверенным данным и никогда их не выполнять.

  • Для рабочих процессов, которым обоснованно требуется доступ к форкам, создайте явную политику событий и используйте таргетинг файлов рабочих процессов, чтобы разрешить pull_request_target только для этих файлов. Относитесь к каждому файлу из этого белого списка как к объекту высокого риска: прогоните его через указанный выше аудит перед добавлением и проводите повторный аудит при любых изменениях.

Для рабочих процессов, которым обоснованно требуется доступ к форкам, создайте явную политику событий и используйте таргетинг файлов рабочих процессов, чтобы разрешить pull_request_target только для этих файлов. Относитесь к каждому файлу из этого белого списка как к объекту высокого риска: прогоните его через указанный выше аудит перед добавлением и проводите повторный аудит при любых изменениях.

Состояние безопасности жизненного цикла разработки (SDLC) в 2026 году

О чём эта статья

Ещё в разделе «Кибербезопасность»

Все →

Ещё от Wiz