
Дифференциальный proof-of-concept для CVE-2026-40176, демонстрирующий внедрение команд ОС в драйвер Perforce в Composer через вредоносный URL-адрес репозитория, с автоматическим A/B-тестированием против уязвимых и исправленных версий.
Автономное, объектно-ориентированное PHP-подтверждение концепции, которое демонстрирует и дифференциально проверяет уязвимость внедрения команд в драйвере репозитория Perforce для Composer.
PoC запускает один и тот же вредоносный composer.json с двумя бинарными файлами Composer — уязвимой версией (2.9.5) и исправленной версией (2.9.6) — и доказывает наличие ошибки, наблюдая за побочным эффектом (файл-маркер, созданный внедренной командой оболочки), который срабатывает на уязвимой версии, но не на исправленной.
⚠️ Только для авторизованных исследований безопасности и защитного тестирования. См. Ответственное использование.
| CVE | CVE-2026-40176 |
| Компонент | Composer — драйвер репозитория/VCS Perforce (perforce) |
| Класс | Внедрение команд ОС через URL репозитория, контролируемый атакующим |
| Поверхность атаки | composer.json, содержащий сконструированную запись repositories с type: perforce |
| Уязвимая версия | Composer 2.9.5 |
| Исправленная версия | Composer 2.9.6 |
| Триггер | Разрешение/обновление зависимостей (composer update) на основе вредоносного манифеста |
| Воздействие | Произвольное выполнение команд на машине, запускающей Composer |
| Язык PoC | PHP (один файл, без внешних зависимостей) |
Composer может разрешать пакеты из нескольких систем контроля версий. Для Perforce репозиторий идентифицируется URL-адресом p4://, который кодирует хост, порт и пользователя/поток. Когда драйвер Perforce в Composer формирует базовую командную строку p4, поля, взятые из контролируемого атакующим URL, недостаточно очищаются перед передачей оболочке.
Поскольку автор манифеста полностью контролирует URL репозитория, атакующий, который может заставить жертву выполнить composer update/composer install на основе вредоносного composer.json (например, отравленная зависимость, враждебный репозиторий или задание CI, обрабатывающее непроверенные файлы проекта), может выйти за пределы предполагаемого вызова p4 и выполнить произвольные команды ОС с привилегиями процесса Composer.
Это относится к тому же семейству, что и исторические проблемы внедрения аргументов в драйверах VCS Composer, где значения URL/ветки/потока попадают в команды оболочки без экранирования. Composer 2.9.6 ужесточает драйвер Perforce, так что внедренная нагрузка больше не выполняется.
Авторитетным описанием поведения, демонстрируемого здесь, является исходный код PoC (
CVE202640176Test.php); за подробностями исправления обращайтесь к официальному уведомлению и журналу изменений Composer.
PoC представляет собой один класс CVE202640176Test, который выполняет контролируемый A/B (дифференциальный) эксперимент:
--version у обоих бинарных файлов Composer (уязвимой 2.9.5 и исправленной 2.9.6) и прерывает выполнение, если один из них не может быть вызван.composer.json, раздел repositories которого содержит запись perforce с вредоносным URL p4://, несущим внедренную команду оболочки.composer update в этой директории.finally всегда восстанавливает исходный composer.json в директории проекта.PASS только тогда, когда запуск на уязвимой версии показывает побочный эффект, а на исправленной — нет.Для каждого запуска validateRun() проверяет три условия:
| Проверка | Что доказывает |
|---|---|
| Файл-маркер существует и содержит идентификатор запуска | Внедренная нагрузка touch/echo действительно выполнилась — то есть внедрение команд удалось. |
Вывод Composer упоминает p4 | Был достигнут путь кода драйвера Perforce (нагрузка обработана правильным компонентом, а не каким-то другим шагом). |
| Распознанная версия Composer соответствует ожидаемой | Запущен правильный бинарный файл (2.9.5 или 2.9.6). |
Запуск считается "OK" только при выполнении всех трех условий. Общий тест проходит, когда запуск на уязвимой версии OK, а на исправленной — нет — точный признак реальной уязвимости, которая впоследствии была исправлена.
Вредоносный URL репозитория создается в writeComposerJson():
p4://127.0.0.1:1666:attacker_user;touch <маркер> && echo '<id_запуска>' > <маркер>:client_test
Разберем по частям:
p4://127.0.0.1:1666:attacker_user — корректно выглядящий URL Perforce (хост, порт 1666, пользователь).;touch <маркер> && echo '<id_запуска>' > <маркер> — внедренные команды оболочки. Ведущий ; завершает предполагаемую команду p4; touch создает файл-маркер, а echo '<id_запуска>' > <маркер> записывает в него уникальный идентификатор запуска, чтобы PoC мог подтвердить, что нагрузка (а не какой-то другой процесс) создала этот файл.:client_test — завершающий текст для сохранения правдоподобности разбора URL.В уязвимом драйвере метасимволы оболочки обрабатываются, и файл-маркер создается. В исправленном драйвере значение правильно экранируется/заключается в кавычки, поэтому та же строка рассматривается как инертные данные, и маркер не появляется.
Примечание: PoC использует уникальный идентификатор запуска с меткой времени и записывает свой маркер внутри изолированной временной директории, поэтому нагрузка является безвредной и самоочищающейся, а не разрушительной.
2.9.5 (уязвимая версия)2.9.6 (исправленная версия)exec() запускает cd … && php …). Разработано для Linux/macOS.composer.json в директории проекта (он считывается при запуске, копируется в каждый временный запуск и восстанавливается после).Вам обычно не нужен работающий сервер Perforce: уязвимость заключается в том, как Composer формирует командную строку
p4, и внедренная нагрузка выполняется до/вместо любого реального подключенияp4. Composer может выдать ошибку подключения Perforce — это ожидаемо и не влияет на доказательство через файл-маркер.
Клонируйте / разместите PoC в рабочей директории.
Предоставьте composer.json в той же директории, что и PoC. Минимального достаточно:
{
"name": "research/cve-2026-40176-poc",
"description": "Базовый манифест для дифференциального PoC CVE-2026-40176",
"require": {}
}
Получите два бинарных файла Composer и разместите их там, где ожидает PoC (по умолчанию показаны):
/usr/local/bin/composer-2.9.5.phar # уязвимая версия
/usr/local/bin/composer-2.9.6.phar # исправленная версия
Вы можете скачать конкретные версии Composer из официального архива, например:
curl -Lo /usr/local/bin/composer-2.9.5.phar https://getcomposer.org/download/2.9.5/composer.phar
curl -Lo /usr/local/bin/composer-2.9.6.phar https://getcomposer.org/download/2.9.6/composer.phar
Если ваши пути отличаются, отредактируйте два аргумента конструктора внизу
CVE202640176Test.php.
php CVE202640176Test.php
Тестовая обвязка запускает обе версии Composer по очереди и выводит окончательный вердикт. Исходный composer.json восстанавливается автоматически, даже если один из запусков завершается неудачей (работа происходит во временных директориях, которые удаляются).
Репозиторий содержит контейнеризованную лабораторию, которая точно воспроизводит среду: среда выполнения PHP CLI плюс две зафиксированные версии Composer по путям, которые ожидает PoC, с полной сетевой изоляцией во время выполнения.
docker compose run --rm poc
Эта команда собирает образ cve-2026-40176-lab:latest (загружая Composer 2.9.5 и 2.9.6 и проверяя каждый --version во время сборки) и запускает дифференциальный тест внутри непривилегированного контейнера без возможности исходящих подключений.
Что гарантирует лаборатория:
poc работает во внутренней сети bridge (нет доступа к хосту/интернету), с cap_drop: ALL и no-new-privileges. Внедренная нагрузка остается внутри контейнера.Перезафиксировать версии (они должны соответствовать двум путям в конструкторе PoC) можно через аргументы сборки:
docker compose build --build-arg COMPOSER_AFFECTED_VERSION=2.9.5 --build-arg COMPOSER_FIXED_VERSION=2.9.6
Опционально — работающий сервер Perforce. Сервис p4d доступен в профиле full-lab (docker compose --profile full-lab up). Доказательство на основе маркера не требует его; он существует для исследователей, которым нужна работающая конечная точка p4://. Обратите внимание, что нагрузка PoC нацелена на 127.0.0.1:1666, поэтому маршрутизация через отдельный контейнер p4d требует указания в URL PoC хоста p4d.
Честный результат: в отношении реальных, опубликованных Composer
2.9.5и2.9.6PoC в настоящее время не срабатывает, и лаборатория сообщаетНЕОПРЕДЕЛЕННО / НЕУДАЧА.
Запуск уязвимого Composer (2.9.5) с вредоносным манифестом PoC вызывает внутри Composer до создания любой команды p4/оболочки:
In PerforceDriver.php line 40:
[ErrorException]
Undefined array key "depot"
PerforceDriver::initialize() первым делом читает $this->repoConfig['depot'], но запись репозитория в PoC содержит только type и url (без ключа depot). Драйвер аварийно завершается на этом этапе, поэтому внедренная нагрузка ;touch <маркер> в URL никогда не достигается, и маркер не создается. Сетевая изоляция не является причиной — та же ошибка возникает при полном доступе к сети.
Что это означает:
НЕОПРЕДЕЛЕННО является свойством нагрузки PoC, а не окружения.depot (и, реалистично, работающую конечную точку p4d в профиле full-lab). Уточнение нагрузки до этого уровня является разработкой эксплойта, выходящей за рамки «запустить лабораторию», и намеренно оставлено за пределами данной работы.«Ожидаемый вывод» ниже — это предполагаемый/идеализированный результат PoC, сохраненный для справки; это не то, что текущая нагрузка производит с реальным драйвером.
Успешная демонстрация выглядит примерно так (пути и идентификаторы будут различаться):
=== CVE-2026-40176 PoC started ===
- Composer 2.9.5 version: 2.9.5
- Composer 2.9.6 version: 2.9.6
Prepared temp dir: /tmp/cve20264176_5_20260610_142233
Written malicious composer.json to /tmp/cve20264176_5_20260610_142233
Running Composer in /tmp/cve20264176_5_20260610_142233…
- Parsed Composer version: 2.9.5
- Marker /tmp/cve20264176_5_.../poc_marker_5.txt created with expected ID.
- Output shows Perforce driver activity.
- Affected run exit code: 1
Prepared temp dir: /tmp/cve20264176_6_20260610_142233
Written malicious composer.json to /tmp/cve20264176_6_20260610_142233
Running Composer in /tmp/cve20264176_6_20260610_142233…
- Parsed Composer version: 2.9.6
✘ Marker file /tmp/cve20264176_6_.../poc_marker_6.txt not found.
- Output shows Perforce driver activity.
- Fixed run exit code: 1
=== CVE-2026-40176 PoC finished ===
=== TEST RESULT: PASS (affected succeeded, fixed failed) ===
Ненулевой код завершения Composer — это нормально — composer update в конечном итоге не может получить (поддельный) пакет. Доказательством является файл-маркер, а не статус завершения Composer.
| Результат | Значение |
|---|---|
| ПРОЙДЕН (уязвимая версия сработала, исправленная — нет) | Подтверждено: 2.9.5 выполнила внедренную команду, 2.9.6 — нет. Воспроизведены как уязвимость, так и ее исправление. |
| НЕОПРЕДЕЛЕННО / НЕУДАЧА | Одна или несколько проверок не сошлись. Проверьте строки ✓/✗ для каждого запуска: неверный путь к бинарному файлу, несоответствие версии, отсутствие маркера у уязвимой версии (различия в окружении/экранировании) или неожиданное создание маркера исправленной версией. |
Распространенные причины неопределенного результата:
PerforceDriver.php:40 с Undefined array key "depot" — в конфигурации репозитория отсутствует ключ depot, поэтому Composer никогда не доходит до пути построения команды p4. Это именно то, что происходит с текущей нагрузкой PoC на реальных 2.9.5/2.9.6 (см. Статус воспроизведения).2.9.5 / 2.9.6.exec() изолирована/отключена.p4 (путь драйвера не был достигнут).CVE2026-40176/
├── CVE202640176Test.php # PoC: класс CVE202640176Test + точка входа
├── composer.json # Базовый манифест, который PoC читает/восстанавливает во время выполнения
├── Dockerfile # Образ лаборатории: PHP CLI + зафиксированные Composer 2.9.5 и 2.9.6
├── docker-compose.yml # Запуск `poc` (+ опциональный `p4d` в профиле full-lab)
├── .dockerignore # Уменьшает контекст сборки
├── README.md # Этот файл
└── .gitignore # Исключает локальное состояние агента/инструментов
Весь PoC состоит из одного файла:
__construct() — сохраняет два пути к бинарным файлам, создает идентификатор запуска с меткой времени, снимает снимок исходного composer.json.run() — управляет предварительной проверкой, запуском на уязвимой версии, запуском на исправленной версии, восстановлением и вердиктом.prepareTempDir() — создает изолированную рабочую директорию для каждого запуска.writeComposerJson() — создает вредоносный манифест с внедренным URL p4://.runComposer() — выполняет composer update (аргументы экранированы с помощью escapeshellarg()) и захватывает вывод и код завершения.validateRun() — проверяет версию, файл-маркер и активность драйвера Perforce.preflightVersion() — читает --version из заданного бинарного файла.composer.json восстанавливается в блоке finally независимо от результата.escapeshellarg() (чтобы PoC случайно не внедрился в собственные вызовы exec()). Уязвимость находится на уровень глубже — в том, как сам Composer формирует команду p4, — именно на это и нацелена нагрузка.touch/echo) в уникальном временном файле, что делает PoC безопасным для многократного запуска без побочных эффектов для хоста.exec("cd … && php …") и нагрузка с ;/&& предполагают оболочку, подобную Unix; Windows как есть не поддерживается.mkdir / права доступа. Временные директории создаются с режимом 0777; ужесточите при работе в общем окружении.Composer X.Y.Z из вывода; необычные баннеры Composer могут помешать сопоставлению.Этот репозиторий существует для понимания и защиты от CVE-2026-40176.
composer install/update на непроверенных файлах composer.json (например, в конвейерах CI, обрабатывающих исходный код сторонних проектов) без изоляции.2.9.5 / 2.9.6)2.9.6 (обратитесь к трекеру безопасности вашего дистрибутива / GitHub Advisory Database для получения авторитетных сведений об исправлении)Автор: Ikarolaborda · PoC от 2026-06-10.