ikarolaborda/CVE-2026-40176

GitHub: ikarolaborda/CVE-2026-40176

验证 Composer Perforce 驱动程序中命令注入漏洞(CVE-2026-40176)的概念验证工具,通过差异化对比受影响与已修复版本来确认漏洞状态。

Stars: 0 | Forks: 0

# CVE-2026-40176 — Composer Perforce 驱动命令注入(概念验证) 这是一个独立的、面向对象风格的 PHP 概念验证(PoC),旨在演示并**差异化验证** [Composer](https://getcomposer.org/) 的 Perforce 仓库驱动程序中的命令注入漏洞。 该 PoC 使用同一个恶意的 `composer.json` 针对两个 Composer 二进制文件运行 —— 一个是**受影响**的版本(`2.9.5`),另一个是**已修复**的版本(`2.9.6`)—— 并通过观察副作用(由注入的 shell 命令写入的标记文件)来验证该漏洞。该副作用在受影响版本中会触发,但在已修复版本中不会触发。 ## 目录 - [概述](#summary) - [漏洞详情](#the-vulnerability) - [PoC 工作原理](#how-the-poc-works) - [注入载荷](#the-injection-payload-explained) - [环境要求](#requirements) - [安装说明](#setup) - [用法](#usage) - [预期输出](#expected-output) - [结果解读](#interpreting-the-result) - [项目结构](#project-structure) - [设计说明](#design-notes) - [限制与已知问题](#limitations--known-issues) - [负责任的使用](#responsible-use) - [参考资料](#references) ## 概述 | | | |---|---| | **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 标识,该 URL 编码了主机、端口和用户/流。当 Composer 的 Perforce 驱动程序构建底层的 `p4` 命令行时,从**攻击者控制的 URL** 中提取的字段在传递给 shell 之前没有得到充分的过滤。 由于清单作者可以完全控制仓库 URL,攻击者如果能诱使受害者针对恶意的 `composer.json` 运行 `composer update`/`composer install`(例如,恶意的依赖项、不怀好意的仓库,或处理不受信任项目文件的 CI 任务),就可以脱离预期的 `p4` 调用,并以 Composer 进程的权限执行任意 OS 命令。 这与历史上 Composer VCS 驱动程序参数注入问题属于同一类别,即 URL/分支/流的值在未经转义的情况下流入了 shell 命令。Composer `2.9.6` 对 Perforce 驱动程序进行了加固,使得注入的载荷不再被执行。 ## PoC 工作原理 该 PoC 是一个单独的类 `CVE202640176Test`,它执行一个受控的 **A/B(差异化)实验**: 1. **预检** —— 查询受影响版本(`2.9.5`)和已修复版本(`2.9.6`)Composer 二进制文件的 `--version`,如果其中任何一个无法调用,则提前中止。 2. **受影响版本运行 (2.9.5)** - 在系统临时路径下创建一个隔离的临时目录。 - 写入一个 `composer.json`,其 `repositories` 部分包含一个带有恶意 `p4://` URL 的 `perforce` 条目,该 URL 带有注入的 shell 载荷。 - 在该目录中运行 `composer update`。 - 验证结果。 3. **已修复版本运行 (2.9.6)** —— 针对修补后的二进制文件重复完全相同的步骤。 4. **恢复** —— `finally` 块始终会恢复项目目录中的原始 `composer.json`。 5. **结论** —— 仅当受影响版本的运行显示了副作用,**并且**已修复版本的运行没有显示时,才打印 `PASS`。 ### 验证(什么情况算作“被利用”) 对于每次运行,`validateRun()` 会检查三件事: | 检查项 | 证明了什么 | |---|---| | **标记文件存在并包含运行 ID** | 注入的 `touch`/`echo` 载荷确实执行了 —— 即命令注入成功。 | | **Composer 输出中提到了 `p4`** | 到达了 Perforce 驱动程序代码路径(载荷是由正确的组件处理的,而不是某个无关的步骤)。 | | **解析出的 Composer 版本 == 预期版本** | 运行的是正确的二进制文件(2.9.5 还是 2.9.6)。 | 只有当这三项都通过时,运行才算“OK”。当受影响版本的运行结果为 OK,而已修复版本的运行结果**不是** OK 时,整体测试才算**通过** —— 这正是一个真实存在且随后被修补的漏洞的精确特征。 ## 注入载荷(解释) 恶意的仓库 URL 是在 `writeComposerJson()` 中构建的: ``` p4://127.0.0.1:1666:attacker_user;touch && echo '' > :client_test ``` 分解如下: - `p4://127.0.0.1:1666:attacker_user` —— 一个看起来格式良好的 Perforce URL(主机、端口 `1666`、用户)。 - `;touch && echo '' > ` —— 注入的 shell 命令。开头的 `;` 终止了预期的 `p4` 命令;`touch` 创建标记文件,而 `echo '' > ` 将唯一的运行 ID 写入其中,以便 PoC 能够确认是该载荷(而不是某个不相关的进程)生成了该文件。 - `:client_test` —— 后缀文本,用于保持 URL 其余部分的解析看起来合理。 在**受影响**的驱动程序上,shell 元字符会被执行,从而创建标记文件。在**已修复**的驱动程序上,该值被正确转义/加引号,因此相同的字符串被视为无效数据,不会出现标记文件。 ## 环境要求 - **PHP** 7.4+(基于 PHP 8.x CLI 开发/测试)。PoC 本身仅使用核心功能 —— 运行此测试工具*不需要* Composer 包。 - **两个 Composer 二进制文件**,以 PHAR 格式提供: - Composer `2.9.5`(受影响) - Composer `2.9.6`(已修复) - 类 POSIX shell 环境(`exec()` 会运行 `cd … && php …`)。专为 Linux/macOS 设计。 - 项目目录中需要有一个基础的 `composer.json`(它会在启动时被读取,复制到每个临时运行目录中,并在执行后恢复)。 ## 安装说明 1. **克隆 / 放置 PoC** 到一个工作目录中。 2. **在 PoC 所在的同一目录中提供一个 `composer.json`**。一个最简的文件就足够了: { "name": "research/cve-2026-40176-poc", "description": "Base manifest for the CVE-2026-40176 differential PoC", "require": {} } 3. **获取两个 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 ## 用法 ``` php CVE202640176Test.php ``` 测试工具会依次运行两个 Composer 版本,并打印最终结论。即使某次运行失败,原始的 `composer.json` 也会自动恢复(工作是在一次性的临时目录中进行的)。 ## 在 Docker 中运行(推荐) 该仓库提供了一个容器化实验环境,可以完全复现运行环境:包含一个 PHP CLI 运行时,以及位于 PoC 预期路径上的两个固定 Composer 版本,并且在运行时完全实现了网络隔离。 ``` docker compose run --rm poc ``` 这将构建 `cve-2026-40176-lab:latest`(下载 Composer **2.9.5** 和 **2.9.6**,并在构建期间验证每个版本的 `--version`),然后在一个无特权、无网络出口的容器中运行差异化测试。 该实验环境保证: - **真实的二进制文件。** 两个 Composer 版本都是从官方存档获取的,并在构建时进行了版本检查 —— 如果固定版本不可用,构建将会显式失败。 - **隔离性。** `poc` 服务运行在 `internal` 桥接网络上(无主机/互联网出口),并设置了 `cap_drop: ALL` 和 `no-new-privileges`。注入载荷将被限制在内。 - **无需主机配置。** 无需在主机上放置 PHAR 文件或手动编辑路径。 通过构建参数重新固定版本(必须与 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`。 ## 复现状态(观察结果) 针对 PoC 的恶意清单运行受影响的 Composer (`2.9.5`) 时,会在 Composer 内部构建任何 `p4`/shell 命令**之前**抛出错误: ``` In PerforceDriver.php line 40: [ErrorException] Undefined array key "depot" ``` `PerforceDriver::initialize()` 首先读取 `$this->repoConfig['depot']`,但 PoC 的仓库条目仅提供了 `type` 和 `url`(没有 `depot` 键)。驱动程序此时会中止,因此永远不会执行 URL 中注入的 `;touch ` 载荷,也不会创建任何标记文件。网络隔离**不是**原因 —— 即使允许完全的网络出口,也会出现相同的错误。 **这意味着:** - Docker 实验环境本身是正确的,并且忠实地针对真正受影响/已修复的二进制文件运行了差异化测试工具。出现 `INCONCLUSIVE` 的结果是 **PoC 载荷** 的属性问题,而不是环境的问题。 - 为了触发实际的 Perforce 命令构建路径,PoC 的仓库配置至少需要一个 `depot` 键(实际上还需要在 `full-lab` 配置下提供一个实时的 `p4d` 端点)。将载荷改进到这一步属于超越了“搭建实验环境”的漏洞利用开发范畴,因此在这里有意被排除在范围之外。 下文的“预期输出”是 PoC **预期的/理想化的**结果,保留仅供参考;它并不是当前载荷针对真实驱动程序所产生的结果。 ## 预期输出 成功的演示大致如下所示(路径和 ID 会有所不同): ``` === 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 的退出状态。 ## 结果解读 | 结果 | 含义 | |---|---| | **PASS(受影响版本成功,已修复版本失败)** | 已确认:2.9.5 执行了注入的命令,而 2.9.6 没有。漏洞及其修复均已复现。 | | **INCONCLUSIVE / FAIL** | 一个或多个检查未对齐。检查每次运行的 `✓`/`✗` 输出行:二进制路径错误、版本不匹配、受影响版本的标记文件缺失(环境/转义差异),或者已修复版本意外创建了标记文件。 | 导致结果不确定的常见原因: - **驱动程序在 `PerforceDriver.php:40` 处中止,提示 `Undefined array key "depot"`** —— 仓库配置缺少 `depot` 键,因此 Composer 从未到达 `p4` 命令构建路径。这就是 PoC 当前载荷针对真实的 `2.9.5`/`2.9.6` 运行时发生的情况(参见[复现状态](#reproduction-status-observed))。 - Composer 二进制路径错误,或者AR 文件实际上不是 `2.9.5` / `2.9.6`。 - 主机 shell 或 PHP `exec()` 被沙箱限制/禁用。 - Composer 输出不包含字符串 `p4`(未到达驱动程序路径)。 ## 项目结构 ``` CVE2026-40176/ ├── CVE202640176Test.php # The PoC: CVE202640176Test class + entry point ├── composer.json # Base manifest the PoC reads/restores at runtime ├── Dockerfile # Lab image: PHP CLI + pinned Composer 2.9.5 & 2.9.6 ├── docker-compose.yml # `poc` runner (+ optional `p4d` under full-lab profile) ├── .dockerignore # Trims the build context ├── README.md # This file └── .gitignore # Excludes local agent/tooling state ``` 整个 PoC 就是一个文件: - `__construct()` —— 存储两个二进制路径,生成带有时间戳的运行 ID,并对原始的 `composer.json` 进行快照。 - `run()` —— 协调预检、受影响版本运行、已修复版本运行、恢复以及最终结论。 - `prepareTempDir()` —— 为每次运行创建一个隔离的工作目录。 - `writeComposerJson()` —— 构建带有注入 `p4://` URL 的恶意清单。 - `runComposer()` —— 执行 `composer update`(参数使用 `escapeshellarg()` 转义)并捕获输出和退出代码。 - `validateRun()` —— 检查版本、标记文件以及 Perforce 驱动程序的活动状态。 - `preflightVersion()` —— 从给定的二进制文件中读取 `--version`。 ## 设计说明 - **隔离与清理。** 每次运行都使用其独立的临时目录;无论结果如何,原始的 `composer.json` 都会在 `finally` 块中恢复。 - **测试工具与载荷转义。** *测试工具*使用 `escapeshellarg()` 转义其自身的 shell 参数(这样 PoC 就不会意外注入到自己的 `exec()` 调用中)。*漏洞*存在于更深的一层 —— 即 Composer 自身如何构建 `p4` 命令 —— 这正是载荷的攻击目标。 - **差异化证明。** 在一次过程中同时运行易受攻击和已修补的二进制文件消除了歧义:相同的输入产生了不同的行为,这比单一的正向观察结果要有力得多的证据。 - **良性载荷。** 注入的命令仅将 `touch`/`echo` 写入一个唯一的临时标记中,使得 PoC 可以安全地重复运行,而不会对主机产生副作用。 ## 限制与已知问题 - **硬编码的二进制路径。** 两个 PHAR 路径是在文件底部内联传递的。请根据您的环境进行修改(或者重构为从 CLI 参数/环境变量中读取)。 - **POSIX 假设。** `exec("cd … && php …")` 模式以及 `;`/`&&` 载荷假设使用的是类 Unix shell;不支持直接在 Windows 上运行。 - **`mkdir` 竞争/权限问题。** 临时目录以 `0777` 模式创建;如果在共享环境中运行,请收紧权限。 - **版本检测基于正则表达式。** 它从输出中解析 `Composer X.Y.Z`;不寻常的 Composer 横幅信息可能会导致匹配失败。 ## 负责任的使用 此仓库的存在是为了**理解和防御** CVE-2026-40176。 - **仅**在您拥有或获得明确授权测试的系统和 Composer 安装上运行它。 - 给防御者的建议:**将 Composer 升级到 2.9.6 或更高版本**,并且切勿在未隔离的情况下,对不受信任的 `composer.json` 文件运行 `composer install`/`update`(例如,在处理第三方项目源代码的 CI pipeline 中)。 - 请勿对您无法控制的系统使用此注入技术。未经授权的利用是非法且不道德的。 ## 参考资料 - [Composer — 官方项目](https://getcomposer.org/) - [Composer 发行版存档](https://getcomposer.org/download/) (用于获取特定的 `2.9.5` / `2.9.6` PHAR 文件) - CVE-2026-40176 官方安全通告和 Composer `2.9.6` 更新日志(请咨询您所用发行版的安全追踪器或 [GitHub 安全公告数据库](https://github.com/advisories) 以获取权威的修复详情) - 关于 Composer VCS-driver 参数注入的背景信息(属于相同的漏洞类别),例如 CVE-2021-29472 *作者:Ikarolaborda · PoC 日期:2026-06-10。*
标签:Composer, Cutter, ffuf, Maven, OpenVAS, PHP, 命令注入, 漏洞验证, 请求拦截