
Лабораторный стенд proof-of-concept, демонстрирующий внедрение команд в workflow dispatch GitHub Actions (CVE-2026-39866). Запускает уязвимую и исправленную версии бок о бок для образовательного анализа.
Лабораторный стенд для проверки концепции внедрения команд в GitHub Actions workflow dispatch
CVE-2026-39866 — это ошибка внедрения команд в release workflow GitHub Actions проекта Lawnchair. Файл release_update.yml передаёт пользовательский ввод напрямую в bash без экранирования, поэтому злоумышленник может внедрять команды. Этот репозиторий запускает уязвимую и исправленную версии бок о бок.
| Поле | Значение |
|---|---|
| CVE ID | CVE-2026-39866 |
| GHSA | GHSA-9prc-pp2c-3427 |
| CWE | CWE-78 (Некорректная нейтрализация специальных элементов, используемых в команде ОС) |
| Продукт | LawnchairLauncher/lawnchair |
| Затронуто | Все версии ниже fcba413 |
| Исправлено | fcba413 |
| Обнаружение | Abhay Kumar (@abhayclasher) |
Уязвимость существует в файле workflow release_update.yml. Действие принимает параметр artifactName и передаёт его напрямую в bash-команду без экранирования.
- name: Rename artifact
run: mv ${{ inputs.artifactName }}.zip lawnchair.zip
GitHub Actions подставляет значение переменной в строку до того, как её выполнит оболочка. Без кавычек вокруг ${{ inputs.artifactName }} оболочка интерпретирует пробелы и точки с запятой как разделители команд.
Если злоумышленник передаст file.zip; touch pwned.txt # в качестве artifactName:
Как GitHub Actions это разбирает:
${{ inputs.artifactName }} → file.zip; touch pwned.txt #
Как оболочка это выполняет:
mv file.zip; touch pwned.txt #.zip lawnchair.zip
Оболочка выполняет mv file.zip, затем touch pwned.txt, а остальное игнорирует как комментарий.
GITHUB_TOKEN или ключей подписи релизов).git clone https://github.com/abhayclasher/CVE-2026-39866.git
cd CVE-2026-39866/app
npm install
node server.js
git clone https://github.com/abhayclasher/CVE-2026-39866.git
cd CVE-2026-39866
docker compose up -d
После запуска сервера посетите эти URL:
| URL | Описание |
|---|---|
http://localhost:3000/workflow/vulnerable | Уязвимый workflow — внедрение команд выполняется при отправке |
http://localhost:3001/workflow/patched | Тот же workflow с применённым патчем fcba413 |
Уязвимая версия выполнит вашу внедрённую команду на раннере. Исправленная версия обрабатывает ввод как литеральную строку.
Вы также можете запустить автономный PoC:
cd poc
node exploit.js
Это запускает минимальный сервер на порту 3000, демонстрирующий ту же ошибку без полного контекста приложения.
1. Злоумышленник контролирует входной параметр artifactName
2. GitHub Actions подставляет ${{ inputs.artifactName }} в YAML
3. Шаг run передаёт это в bash
4. Bash видит неэкранированные специальные символы и трактует их как синтаксис команд
5. Команды выполняются с привилегиями окружения раннера
┌────────────────────────────────────────────────────┐
│ 1. Вредоносный ввод: │
│ │
│ artifactName = "file.zip; id #" │
│ │
│ 2. GitHub Actions подставляет в YAML: │
│ │
│ run: mv file.zip; id #.zip lawnchair.zip │
│ │
│ 3. Bash видит: │
│ - mv file.zip │
│ - id <-- Произвольная команда│
│ - #.zip lawnchair.zip (игнорируется) │
│ │
│ 4. Оболочка выполняет произвольную команду │
└────────────────────────────────────────────────────┘
Версия fcba413 добавляет экранирование кавычками вокруг интерполируемой переменной:
- name: Rename artifact
run: mv "${{ inputs.artifactName }}.zip" lawnchair.zip
Кавычки не позволяют оболочке разбирать специальные символы. Полезная нагрузка остаётся обработанной как литеральная строка имени файла.
Лучшая практика — использовать переменные окружения для полной изоляции пользовательского ввода:
- name: Rename artifact
env:
ARTIFACT_NAME: ${{ inputs.artifactName }}
run: mv "$ARTIFACT_NAME.zip" lawnchair.zip
Посмотреть полный коммит: fcba413
CVE-2026-39866/
├── README.md # Этот файл
├── docker-compose.yml # Конфигурация Docker-лаборатории
├── app/
│ ├── Dockerfile # Конфигурация сборки контейнера
│ ├── package.json # Зависимости Node.js
│ └── server.js # Симуляция уязвимого + исправленного workflow раннера
└── poc/
└── exploit.js # Автономный минимальный proof-of-concept
Сообщено Abhay Kumar. Этот репозиторий предназначен только для локального образовательного использования. Не разворачивайте уязвимый сервер в публичной сети.
Только в образовательных целях — используйте в изолированных средах