
CVE-2026-40176에 대한 차등 개념 증명으로, 악성 저장소 URL을 통해 Composer의 Perforce 드라이버에서 OS 명령 삽입을 보여주며, 영향을 받는 버전과 패치된 버전에 대한 자동 A/B 테스트를 포함합니다.
자체 포함된 OOP 스타일 PHP 개념 증명으로, Composer의 Perforce 저장소 드라이버에서 명령 주입 취약점을 시연하고 차등 검증합니다.
PoC는 동일한 악성 composer.json을 두 개의 Composer 바이너리(영향을 받는 **릴리스(2.9.5)**와 수정된 릴리스(2.9.6))에 대해 실행하여, 영향을 받는 버전에서만 실행되고 수정된 버전에서는 실행되지 않는 부작용(주입된 셸 명령으로 작성된 마커 파일)을 관찰함으로써 버그를 증명합니다.
⚠️ 인가된 보안 연구 및 방어 테스트 전용입니다. 책임 있는 사용을 참조하세요.
| CVE | CVE-2026-40176 |
| 구성 요소 | Composer — Perforce (perforce) 저장소/VCS 드라이버 |
| 클래스 | 공격자가 제어하는 저장소 URL을 통한 OS 명령 주입 |
| 공격 표면 | type: perforce의 조작된 repositories 항목을 포함한 composer.json |
| 영향 받는 버전 | Composer 2.9.5 |
| 수정된 버전 | Composer 2.9.6 |
| 트리거 | 악성 매니페스트에 대한 의존성 해결/업데이트 (composer update) |
| 영향 | Composer를 실행하는 머신에서 임의 명령 실행 |
| PoC 언어 | PHP (단일 파일, 외부 종속성 없음) |
Composer는 여러 버전 관리 시스템에서 패키지를 해결할 수 있습니다. Perforce의 경우 호스트, 포트 및 사용자/스트림을 인코딩하는 p4:// URL로 저장소가 식별됩니다. Composer의 Perforce 드라이버가 기본 p4 명령줄을 구축할 때, 공격자가 제어하는 URL에서 가져온 필드가 셸에 전달되기 전에 충분히 삭제되지 않습니다.
매니페스트 작성자는 저장소 URL을 완전히 제어할 수 있으므로, 피해자가 악성 composer.json에 대해 composer update/composer install을 실행하도록 할 수 있는 공격자(예: 감염된 의존성, 적대적인 저장소, 신뢰할 수 없는 프로젝트 파일을 처리하는 CI 작업)는 의도된 p4 호출을 벗어나 Composer 프로세스의 권한으로 임의의 OS 명령을 실행할 수 있습니다.
이는 URL/브랜치/스트림 값이 이스케이프 없이 셸 명령으로 전달되는 기존 Composer VCS 드라이버 인수 주입 문제와 같은 계열에 속합니다. Composer 2.9.6은 Perforce 드라이버를 강화하여 주입된 페이로드가 더 이상 실행되지 않도록 합니다.
여기서 시연된 동작에 대한 권위 있는 설명은 PoC 소스 자체(
CVE202640176Test.php)입니다. 업스트림 수정 사항에 대한 자세한 내용은 공식 권고 및 Comporter 변경 로그를 참조하십시오.
PoC는 단일 클래스 CVE202640176Test로, 제어된 A/B(차등) 실험을 수행합니다:
2.9.5) 및 수정된 (2.9.6) Composer 바이너리 모두에서 --version을 조회하고, 둘 중 하나라도 호출할 수 없으면 조기 종료합니다.repositories 섹션에 주입된 셸 페이로드를 포함하는 악성 p4:// URL이 있는 perforce 항목이 포함된 composer.json을 작성합니다.composer update를 실행합니다.finally 블록은 항상 프로젝트 디렉터리의 원래 composer.json을 복원합니다.PASS를 출력합니다.각 실행에 대해 validateRun()은 세 가지를 확인합니다:
| 검사 항목 | 증명하는 내용 |
|---|---|
| 마커 파일이 존재하고 실행 ID를 포함함 | 주입된 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 <marker> && echo '<runId>' > <marker>:client_test
분석:
p4://127.0.0.1:1666:attacker_user — 올바른 형식의 Perforce URL (호스트, 포트 1666, 사용자).;touch <marker> && echo '<runId>' > <marker> — 주입된 셸 명령입니다. 선행 ;는 의도된 p4 명령을 종료합니다. touch는 마커 파일을 생성하고 echo '<runId>' > <marker>는 고유한 실행 ID를 파일에 기록하여 PoC가 페이로드(관련 없는 프로세스가 아닌)가 파일을 생성했음을 확인할 수 있도록 합니다.:client_test — URL 파싱이 그럴듯하게 유지되도록 하는 후행 텍스트입니다.영향을 받는 드라이버에서는 셸 메타문자가 처리되어 마커 파일이 생성됩니다. 수정된 드라이버에서는 값이 적절하게 이스케이프/인용되므로 동일한 문자열이 비활성 데이터로 처리되어 마커가 나타나지 않습니다.
참고: PoC는 고유한 타임스탬프가 있는 실행 ID를 사용하고 격리된 임시 디렉터리 내부에 마커를 작성하므로 페이로드는 무해하고 자체 정리되며 파괴적이지 않습니다.
2.9.5 (영향 받는 버전)2.9.6 (수정된 버전)exec()는 cd … && php …를 실행합니다). Linux/macOS용으로 설계되었습니다.composer.json (시작 시 읽히고 각 임시 실행에 복사된 후 복원됨).일반적으로 라이브 Perforce 서버가 필요하지 않습니다. 취약점은 Composer가
p4명령줄을 구축하는 방식에 있으며, 주입된 페이로드는 실제p4연결 전/주변에서 실행됩니다. Composer는 Perforce 연결 오류를 기록할 수 있습니다. 이는 예상된 것이며 마커 파일 증명에 영향을 미치지 않습니다.
PoC를 작업 디렉터리에 복제/배치합니다.
PoC와 동일한 디렉터리에 composer.json을 제공합니다. 최소한의 것으로 충분합니다:
{
"name": "research/cve-2026-40176-poc",
"description": "CVE-2026-40176 차등 PoC용 기본 매니페스트",
"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 런타임과 PoC가 예상하는 경로에 있는 두 개의 고정 Composer 릴리스, 런타임에 완전히 네트워크 격리됨.
docker compose run --rm poc
이 명령은 cve-2026-40176-lab:latest를 빌드하고(빌드 중 Composer 2.9.5 및 2.9.6을 다운로드하고 각 --version을 확인) 권한이 없고 외부 송신이 없는 컨테이너 내에서 차등 테스트를 실행합니다.
랩이 보장하는 사항:
poc 서비스는 internal 브리지 네트워크(호스트/인터넷 송신 없음)에서 실행되며, 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 서버. full-lab 프로필에서 p4d 서비스를 사용할 수 있습니다(docker compose --profile full-lab up). 마커 기반 증명에는 필요하지 않습니다. 라이브 p4:// 엔드포인트를 원하는 연구자를 위해 존재합니다. PoC 페이로드는 127.0.0.1:1666을 대상으로 하므로 별도의 p4d 컨테이너를 통한 라우팅은 PoC URL을 호스트 p4d로 지정해야 합니다.
정직한 결과: 실제 게시된 Composer
2.9.5및2.9.6에 대해 PoC는 현재 실행되지 않으며, 랩은INCONCLUSIVE / FAIL을 보고합니다.
영향을 받는 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" 키 없음). 드라이버가 이 시점에서 중단되므로 URL의 주입된 ;touch <marker> 페이로드에 도달하지 못하고 마커가 생성되지 않습니다. 네트워크 격리가 원인이 아닙니다 — 완전한 송신이 있는 경우에도 동일한 오류가 발생합니다.
이것이 의미하는 바:
INCONCLUSIVE 결과는 PoC 페이로드의 속성이며 환경의 속성이 아닙니다.depot 키(그리고 현실적으로 full-lab 프로필 아래의 라이브 p4d 엔드포인트)가 필요합니다. 페이로드를 해당 시점까지 정제하는 것은 "랩을 설정하는 것"을 넘어서는 익스플로잇 개발이며 의도적으로 여기서는 범위를 벗어납니다.아래 "예상 출력"은 PoC의 의도된/이상적인 결과이며 참조용으로 유지됩니다. 현재 페이로드가 실제 드라이버에 대해 생성하는 결과가 아닙니다.
성공적인 시연은 대략 다음과 같습니다(경로 및 ID는 다를 수 있음):