0xmrma/CVE-2026-42089

GitHub: 0xmrma/CVE-2026-42089

该报告详细披露了 yeoman-environment 中因静默自动安装缺失包而导致的信任边界失效与任意代码执行漏洞(CVE-2026-42089)。

Stars: 0 | Forks: 0

# CVE-2026-42089 一个本地包安装辅助程序过度信任了调用方提供的包名。在 yeoman-environment 中,缺失的 generator 可以在未经用户确认的情况下被安装,从而将攻击者控制的项目元数据转变为一条包安装与代码执行路径。 ## 简介 我在审查 **generator-jhipster** 时发现了这个问题,当时脑海中有一个简单的安全疑问: **攻击者控制的项目元数据是否能在用户明确要求之前,就让开发者工具去获取并执行第三方代码?** 在这个案例中,答案是肯定的。 最初看起来像是一个 JHipster 的问题,但实际上在 **yeoman-environment** 中有着更深的上游根本原因。 漏洞行为存在于 Yeoman 的本地 generator 安装流程中,在该流程中,由调用方提供的缺失包会在未经用户确认的情况下被自动安装。在将这些攻击者控制的包名传递到该路径的下游消费者中,这足以创建一条真实的包安装与代码执行链条。 该问题最终成为了 **CVE-2026-42089**。 **yeoman-environment:** [GitHub 上的 yeoman-environment](https://github.com/yeoman/environment) **包:** `yeoman-environment` (npm) **CVE:** CVE-2026-42089 这影响了 **yeoman-environment**,即 Yeoman 背后负责 generator 加载和引导流程的 runtime 层。官方项目将其描述为处理 generator 生命周期和发现的组件,截至 **2026 年 6 月 26 日**,该 npm 包页面显示其**每周下载量达到 1,466,426 次**,这使其成为 JavaScript 工具生态系统中广泛部署的包。 photo0 ## 攻击链 `攻击者控制的项目配置 -> 调用方提供的 generator 包名 -> yeoman-environment 静默安装缺失的包 -> 下游工具加载已安装的 generator 代码 -> 在 CLI bootstrap 期间进行包安装和代码执行` ## yeoman-environment 的作用 **yeoman-environment** 是基于 Yeoman 的工具背后的 runtime 和 generator 加载层。 除了其他功能外,它还负责处理: - generator 查找 - 本地仓库管理 - 为缺失的 generator 安装包 - generator 的注册与加载 这意味着它直接处于信任边界上。 相关的问题不在于 Yeoman 是否“仅仅是一个本地工具”。 真正的问题是,**不受信任的输入是否能影响包安装和代码加载行为**。 在这个案例中,是可以的。 ## 为什么这个攻击面值得关注 我在这里寻找的不是内存损坏或仅仅是崩溃的 bug。 更强的目标是扩展和包解析的攻击面。 任何具备以下特征的系统: - 从另一层接受包名, - 自动安装它们, - 然后使其可供加载 都值得密切 scrutinize。 当下游消费者可以从项目本地数据中推导出这些包名时,尤其如此。 这正是普通配置可能悄然成为安全边界的地方。 这正是值得深入调查的正确位置。 ## 我关注的边界 我首先通过 **generator-jhipster** 重现了该行为。 重要路径如下: - 项目本地的 `.yo-rc.json` 声明了一个 blueprint 包 - JHipster 在 CLI bootstrap 期间读取该 blueprint 条目 - 缺失的 blueprint 包被传递到 Yeoman 的安装路径中 - Yeoman 静默安装它们 - 下游逻辑随后导入 blueprint CLI 模块 这意味着即使是一个无害的命令,例如: ``` jhipster --help ``` 也可能在请求的命令完成之前就触发包安装。 这是真正的信任边界失效。 下游触发条件有助于暴露它,但不安全的默认行为存在于 Yeoman 中。 ## 根本原因 这个 bug 很简单。 在 `yeoman-environment` 中,存在漏洞的方法是: ``` async installLocalGenerators(packages) { const entries = Object.entries(packages); const specs = entries.map(([packageName, version]) => `${packageName}${version ? `@${version}` : ''}`); const installResult = await this.repository.install(specs); const failToInstall = installResult.find(result => !result.path); if (failToInstall) { throw new Error(`Fail to install ${failToInstall.pkgid}`); } await this.lookup({ packagePaths: installResult.map(result => result.path) }); return true; } ``` 该方法直接通过以下方式安装调用方提供的包名: ``` this.repository.install(specs) ``` 而事先并未提示用户。 这就是核心漏洞。 ### 为什么这是可利用的 因为包名不一定非要来自受信任的源。 如果下游消费者从攻击者控制的项目元数据中推导出包名,那么攻击链就很简单: - 攻击者间接控制包名 - 下游工具将它们传递给 Yeoman - Yeoman 静默安装它们 - 下游代码继续执行,新安装的包可供加载 这不仅仅是“发生了包安装”。 这是不受信任的输入在没有明确同意边界的情况下,跨越到了包安装接收端 (sink)。 ## 为什么这是一个安全问题,而不仅仅是工具行为 重要的区别在于,这是源自不受信任输入的静默安装。 以下两者之间有实质性的区别: - 用户明确决定安装一个包,与 - 框架因为项目本地数据导致调用方请求安装而静默安装包 当包在随后立即可被加载时,这种区别就变得更加重要了。 问题不在于存在第三方 generator。 问题在于,**Yeoman 默认将调用方提供的包名视为可直接安装,而无需用户确认**。 这使得不安全的下游信任假设在实质上变得更加糟糕。 这正是为什么修复程序添加了确认关卡的原因。 ## PoC 我使用了两个层面的证明,因为它们同时展示了根本原因和真实的下游影响。 ### PoC 1:原生下游触发 第一个证明使用了未经修改的 `generator-jhipster`。 我创建了一个项目,其根目录下的 `.yo-rc.json` 引用了一个尚未安装的 blueprint 包,然后运行了: ``` jhipster --help ``` 这导致 JHipster 在 help 命令完成之前,将缺失的 blueprint 传递到了 Yeoman 的本地 generator 安装流程中。 重要结果是: - 一个看起来无害的命令触发了包解析和安装行为 - 仅凭项目本地元数据就足以触发安装路径 这清楚地确立了真实的触发条件。 ### PoC 2:受控的包执行路径 第二个证明使用了受控的本地 registry 和一个旨在安全展示导入时副作用的包。 这很重要,因为我想展示更有力的事实: - 项目本地元数据影响包选择 - Yeoman 静默安装该包 - 下游逻辑加载已安装的 blueprint CLI 模块 - 在 bootstrap 期间代码执行变得可触达 这是最有力的证据链,因为它将问题从单纯的: 推进到了: 在这一点上,信任边界的失效变得更加难以忽视。 ## 为什么这样选择 PoC 第一个 PoC 证明了静默安装行为。 第二个 PoC 证明了为什么该行为至关重要。 这种拆分很重要。 一份止步于: 的报告,其说服力不如一份展示了以下内容的报告: - 攻击者控制的输入到达了安装路径 - 安装在没有确认的情况下发生 - 下游逻辑使代码执行变得可触达 这才是完整的事实。 ## 受影响范围 在通报处理和本地审查期间,该行为被追溯到 `installLocalGenerators()` 的引入处: ``` yeoman-environment 2.9.0 ``` 因此,受影响的范围是: ``` >= 2.9.0 and < 6.0.1 ``` 已修复的版本是: ``` 6.0.1 ``` ## 修复分析 修复是正确且最小化的。 在 `6.0.1` 版本中,`installLocalGenerators()` 被修改为在安装之前添加了确认步骤,除非明确请求了 force-install。 修复后的形式如下所示: ``` async installLocalGenerators(packages, forceInstall = false) { ``` 然后是: ``` const { aproveInstall } = await this.adapter.prompt({ message: `The following packages need to be installed in the local repository: ${specs.join(', ')}. Do you want to proceed?`, type: 'confirm', name: 'aproveInstall', default: false, }); ``` 如果用户拒绝,则中止安装。 这是正确的修复方式,因为它恢复了缺失的信任边界: - 调用方提供的包名默认不再被静默安装 - 需要明确的用户批准 - 下游工具不能再依赖意外的隐式信任 此修复通过以下方式落实到: ``` 78d2af7 ``` 并借由: ``` PR #753 ``` 这正是你在此类安全问题上希望看到的修复方式: - 小巧 - 直接 - 易于推理 - 与存在漏洞的 sink 本身紧密相关 ## 严重性与分类 这个问题得到了合理的重视,因为其影响不仅仅是表面的或令人惊讶的行为。 该漏洞行为可导致: - 攻击者选择的包被安装 - 在无害的命令路径中访问包基础设施的网络权限 - 下游代码加载的可达性 - 受影响消费者的开发环境被入侵 与该问题相关的 CVSS 向量为: ``` CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H ``` 这对于下游的攻击经历来说是合理的: - 本地执行上下文 - 低复杂度 - 无需先前的权限 - 需要用户交互 - 一旦触达包执行路径,将对机密性、完整性和可用性产生严重影响 ## 披露 该问题最初是作为针对 **generator-jhipster** 的私下报告提交的,因为那是我最初验证的真实触发路径。 在分类审查期间,JHipster 维护者指出,自动安装行为本身存在于 **yeoman-environment** 中,并引用了上游的修复。 这促成了正确的转向: - 将根本原因缩小到 Yeoman - 将 JHipster 视为受影响的下游消费者 - 私下向上游报告该问题 Yeoman 维护者审查了该问题,确认了受影响的范围,并通过私下安全通报进行了追踪。 该报告后来被分配了: **CVE-2026-42089** 该通报还记录了通过 `generator-jhipster` 触发的真实下游路径。 这是一个很好的例子,说明了为什么协调披露有时需要额外的一个步骤: - 首先确定实际的触发点 - 然后确定真正的责任归属边界 在这里,下游重现非常有用,但上游包才是 CVE 的正确归属。 ## 这个 Bug 真正教会了我们什么 这里的关键教训很简单: 很多人本能地低估了像这样的问题,因为它们发生在 CLI 工具中。 这是一个错误。 真正的问题不在于该工具是否是本地的。 真正的问题是: **不受信任的输入能否在没有用户明确决定的情况下,让工具去获取并信任代码?** 在这个案例中,是可以的。 这才是真正的要点。 这个问题还强化了关于优秀漏洞研究的一些重要启示: - 你用来重现问题的第一个产品并不总是真正的根本原因所有者 - 下游 PoC 往往能让风险变得显而易见 - 上游的信任边界失误才是真正应该实施修复的地方 这正是这个 CVE 的形态。 ## 关键点 - 包安装辅助程序是安全边界 - 默认情况下不应静默安装调用方提供的包名 - 当本地项目元数据影响扩展加载时,可能会变得很危险 - 在 generator-jhipster 中的下游重现清晰地暴露了该问题 - 根本原因仍然归属于 yeoman-environment - 添加明确的确认关卡是正确的修复方法 ## 结语 这个漏洞与花哨的 payload 无关。 它关乎的是提出正确的信任边界问题。 一个下游工具让项目本地数据影响了包选择。 Yeoman 在未经确认的情况下安装了缺失的包。 其余的代码加载链条完成了剩下的工作。 这就是为什么它成为了 **CVE-2026-42089**。 在 **yeoman-environment 6.0.1** 中已修复。
标签:CMS安全, JavaScript, MITM代理, SOC Prime, Yeoman, 开发工具, 数据可视化, 暗色界面, 漏洞分析, 脚手架框架, 路径探测