依赖混淆
Reading time: 13 minutes
tip
学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
学习和实践 Azure 黑客技术:
HackTricks Training Azure Red Team Expert (AzRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。
基本信息
依赖混淆(又称替代攻击)发生在包管理器从意外的、可信度较低的注册表/源(通常是公共注册表)解析依赖名称,而不是预期的私有/内部注册表。这通常导致安装一个攻击者控制的包。
常见根本原因:
- 拼写错误/错别字:导入
reqests
而不是requests
(从公共注册表解析)。 - 不存在/被遗弃的内部包:导入
company-logging
,该包在内部不再存在,因此解析器在公共注册表中查找并找到攻击者的包。 - 跨多个注册表的版本偏好:导入内部的
company-requests
,而解析器被允许查询公共注册表,并优先选择攻击者公开发布的“最佳”/更新版本。
关键思想:如果解析器可以看到同一包名称的多个注册表,并且被允许全局选择“最佳”候选项,则您处于脆弱状态,除非您限制解析。
利用
warning
在所有情况下,攻击者只需发布一个与您的构建从公共注册表解析的依赖同名的恶意包。安装时的钩子(例如,npm 脚本)或导入时的代码路径通常会导致代码执行。
拼写错误和不存在
如果您的项目引用了在私有注册表中不可用的库,并且您的工具回退到公共注册表,攻击者可以在公共注册表中种植一个具有该名称的恶意包。您的运行器/CI/开发机器将获取并执行它。
未指定版本 / 跨索引的“最佳版本”选择
开发人员经常不固定版本或允许广泛的范围。当解析器配置了内部和公共索引时,它可能会选择最新版本,而不考虑来源。对于内部名称如 requests-company
,如果内部索引有 1.0.1
,但攻击者在公共注册表中发布了 1.0.2
,并且您的解析器考虑了这两个版本,则公共包可能会胜出。
AWS 修复
此漏洞在 AWS CodeArtifact 中被发现(在此博客文章中阅读详细信息)。AWS 添加了控制措施,以将依赖项/源标记为内部或外部,以便客户端不会从上游公共注册表获取“内部”名称。
查找易受攻击的库
在关于依赖混淆的原始帖子中,作者查找了数千个暴露的清单(例如,package.json
、requirements.txt
、锁定文件),以推断内部包名称,然后将更高版本的包发布到公共注册表。
实用攻击者手册(针对授权测试中的红队)
- 枚举名称:
- 在代码库和 CI 配置中 grep 清单/锁定文件和内部命名空间。
- 查找特定于组织的前缀(例如,
@company/*
、company-*
、内部 groupIds、NuGet ID 模式、Go 的私有模块路径等)。 - 检查公共注册表的可用性:
- 如果名称在公共注册表中未注册,则注册它;如果存在,则通过针对内部传递名称尝试子依赖劫持。
- 发布优先:
- 选择一个“胜出”的 semver(例如,非常高的版本)或符合解析器规则。
- 在适用时包含最小的安装时执行(例如,npm
preinstall
/install
/postinstall
脚本)。对于 Python,优先选择导入时执行路径,因为 wheel 通常在安装时不会执行任意代码。 - 外泄控制:
- 确保 CI 到您控制的端点的出站连接被允许;否则,使用 DNS 查询或错误消息作为侧信道来证明代码执行。
caution
始终获得书面授权,为参与使用唯一的包名称/版本,并在测试结束时立即取消发布或协调清理。
防御者手册(实际防止混淆的方法)
适用于各个生态系统的高层次策略:
- 使用唯一的内部命名空间,并将其绑定到单个注册表。
- 避免在解析时混合信任级别。优先选择一个内部注册表,该注册表代理批准的公共包,而不是同时提供内部和公共端点给包管理器。
- 对于支持的管理器,将包映射到特定源(在注册表之间没有全局“最佳版本”)。
- 固定和锁定:
- 使用记录解析的注册表 URL 的锁定文件(npm/yarn/pnpm)或使用哈希/证明固定(pip
--require-hashes
,Gradle 依赖验证)。 - 在注册表/网络层阻止内部名称的公共回退。
- 在可行的情况下,在公共注册表中保留您的内部名称,以防止未来的抢注。
生态系统注意事项和安全配置片段
以下是减少或消除依赖混淆的务实、最小配置。优先在 CI 和开发环境中强制执行这些配置。
JavaScript/TypeScript (npm, Yarn, pnpm)
- 对所有内部代码使用作用域包,并将作用域固定到您的私有注册表。
- 在 CI 中保持安装不可变(npm 锁定文件,
yarn install --immutable
)。
.npmrc(项目级)
# Bind internal scope to private registry; do not allow public fallback for @company/*
@company:registry=https://registry.corp.example/npm/
# Always authenticate to the private registry
//registry.corp.example/npm/:_authToken=${NPM_TOKEN}
strict-ssl=true
package.json (用于内部包)
{
"name": "@company/api-client",
"version": "1.2.3",
"private": false,
"publishConfig": {
"registry": "https://registry.corp.example/npm/",
"access": "restricted"
}
}
Yarn Berry (.yarnrc.yml)
npmScopes:
company:
npmRegistryServer: "https://registry.corp.example/npm/"
npmAlwaysAuth: true
# CI should fail if lockfile would change
enableImmutableInstalls: true
操作提示:
- 仅在
@company
范围内发布内部包。 - 对于第三方包,通过您的私有代理/镜像允许公共注册,而不是直接从客户端。
- 考虑为您发布的公共包启用 npm 包来源,以增加可追溯性(这本身并不能防止混淆)。
Python (pip / Poetry)
核心规则:不要使用 --extra-index-url
来混合信任级别。要么:
- 暴露一个单一的内部索引,该索引代理并缓存已批准的 PyPI 包,或者
- 使用显式索引选择和哈希固定。
pip.conf
[global]
index-url = https://pypi.corp.example/simple
# Disallow source distributions when possible
only-binary = :all:
# Lock with hashes generated via pip-tools
require-hashes = true
使用 pip-tools 生成哈希要求:
# From pyproject.toml or requirements.in
pip-compile --generate-hashes -o requirements.txt
pip install --require-hashes -r requirements.txt
如果您必须访问公共 PyPI,请通过内部代理进行,并在其中维护明确的允许列表。避免在 CI 中使用 --extra-index-url
。
.NET (NuGet)
使用包源映射将包 ID 模式与明确的源绑定,以防止从意外的源解析。
nuget.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="corp" value="https://nuget.corp.example/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
<packageSource key="corp">
<package pattern="Company.*" />
<package pattern="Internal.Utilities" />
</packageSource>
</packageSourceMapping>
</configuration>
Java (Maven/Gradle)
Maven settings.xml (将所有内容镜像到内部;通过 Enforcer 禁止 POM 中的临时仓库):
<settings>
<mirrors>
<mirror>
<id>internal-mirror</id>
<mirrorOf>*</mirrorOf>
<url>https://maven.corp.example/repository/group</url>
</mirror>
</mirrors>
</settings>
添加 Enforcer 以禁止在 POM 中声明的仓库并强制使用您的镜像:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.6.1</version>
<executions>
<execution>
<id>enforce-no-repositories</id>
<goals><goal>enforce</goal></goals>
<configuration>
<rules>
<requireNoRepositories />
</rules>
</configuration>
</execution>
</executions>
</plugin>
Gradle: 集中管理和锁定依赖项。
- 仅在
settings.gradle(.kts)
中强制使用仓库:
dependencyResolutionManagement {
repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS
repositories {
maven { url = uri("https://maven.corp.example/repository/group") }
}
}
- 启用依赖验证(校验和/签名)并提交
gradle/verification-metadata.xml
。
Go Modules
配置私有模块,以便不使用公共代理和校验和数据库。
# Use corporate proxy first, then public proxy as fallback
export GOPROXY=https://goproxy.corp.example,https://proxy.golang.org
# Mark private paths to skip proxy and checksum db
export GOPRIVATE=*.corp.example.com,github.com/your-org/*
export GONOSUMDB=*.corp.example.com,github.com/your-org/*
Rust (Cargo)
将 crates.io 替换为经过批准的内部镜像或供应商目录进行构建;不允许任意公共回退。
.cargo/config.toml
[source.crates-io]
replace-with = "corp-mirror"
[source.corp-mirror]
registry = "https://crates-mirror.corp.example/index"
对于发布,明确使用 --registry
并将凭据限制在目标注册表中。
Ruby (Bundler)
使用源块并禁用多源 Gemfiles,以便 gem 仅来自预期的存储库。
Gemfile
source "https://gems.corp.example"
source "https://rubygems.org" do
gem "rails"
gem "pg"
end
source "https://gems.corp.example" do
gem "company-logging"
end
在配置级别强制执行:
bundle config set disable_multisource true
CI/CD 和注册表控制措施
- 私有注册表作为单一入口:
- 使用 Artifactory/Nexus/CodeArtifact/GitHub Packages/Azure Artifacts 作为开发者/CI 可以访问的唯一端点。
- 实施阻止/允许规则,以确保内部命名空间永远不会从上游公共源解析。
- 锁定文件在 CI 中是不可变的:
- npm:提交
package-lock.json
,使用npm ci
。 - Yarn:提交
yarn.lock
,使用yarn install --immutable
。 - Python:提交哈希的
requirements.txt
,强制使用--require-hashes
。 - Gradle:提交
verification-metadata.xml
,并在未知工件上失败。 - 出站出口控制:阻止 CI 直接访问公共注册表,除非通过批准的代理。
- 名称保留:在支持的公共注册表中预注册您的内部名称/命名空间。
- 包的来源/证明:在发布公共包时,启用来源/证明,以使篡改在下游更易被检测。
参考文献
- https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610
- https://zego.engineering/dependency-confusion-in-aws-codeartifact-86b9ff68963d
- https://learn.microsoft.com/en-us/nuget/consume-packages/package-source-mapping
- https://yarnpkg.com/configuration/yarnrc/
tip
学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
学习和实践 Azure 黑客技术:
HackTricks Training Azure Red Team Expert (AzRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。