From 5be57cab06dcfaed70c6a80dd37b391c9ed81943 Mon Sep 17 00:00:00 2001 From: Translator Date: Thu, 21 Aug 2025 02:39:44 +0000 Subject: [PATCH] Translated ['', 'src/pentesting-web/dependency-confusion.md'] to zh --- src/pentesting-web/dependency-confusion.md | 256 +++++++++++++++++++-- 1 file changed, 239 insertions(+), 17 deletions(-) diff --git a/src/pentesting-web/dependency-confusion.md b/src/pentesting-web/dependency-confusion.md index 4f288a884..bc33ff230 100644 --- a/src/pentesting-web/dependency-confusion.md +++ b/src/pentesting-web/dependency-confusion.md @@ -4,40 +4,262 @@ ## 基本信息 -总之,依赖混淆漏洞发生在项目使用了一个**拼写错误**、**不存在**或**未指定版本**的库,并且所使用的依赖库仓库允许从**公共**仓库**获取更新版本**。 +依赖混淆(又称替代攻击)发生在包管理器从意外的、可信度较低的注册表/源(通常是公共注册表)解析依赖名称,而不是预期的私有/内部注册表。这通常导致安装一个攻击者控制的包。 -- **拼写错误**:导入**`reqests`**而不是`requests` -- **不存在**:导入`company-logging`,一个**不再存在**的内部库 -- **未指定版本**:导入一个**内部**的**存在**的`company-requests`库,但仓库检查**公共仓库**以查看是否有**更高版本**。 +常见根本原因: +- 拼写错误/错别字:导入 `reqests` 而不是 `requests`(从公共注册表解析)。 +- 不存在/被遗弃的内部包:导入 `company-logging`,该包在内部不再存在,因此解析器在公共注册表中查找并找到攻击者的包。 +- 跨多个注册表的版本偏好:导入内部的 `company-requests`,而解析器被允许查询公共注册表,并优先选择攻击者公开发布的“最佳”/更新版本。 + +关键思想:如果解析器可以看到同一包名称的多个注册表,并且被允许全局选择“最佳”候选项,则您处于脆弱状态,除非您限制解析。 ## 利用 > [!WARNING] -> 在所有情况下,攻击者只需发布一个**恶意包,名称**与受害公司使用的库相同。 +> 在所有情况下,攻击者只需发布一个与您的构建从公共注册表解析的依赖同名的恶意包。安装时的钩子(例如,npm 脚本)或导入时的代码路径通常会导致代码执行。 -### 拼写错误与不存在 +### 拼写错误和不存在 -如果您的公司试图**导入一个不是内部的库**,那么库的仓库很可能会在**公共仓库**中搜索它。如果攻击者创建了它,您的代码和运行的机器很可能会被攻陷。 +如果您的项目引用了在私有注册表中不可用的库,并且您的工具回退到公共注册表,攻击者可以在公共注册表中种植一个具有该名称的恶意包。您的运行器/CI/开发机器将获取并执行它。 -### 未指定版本 +### 未指定版本 / 跨索引的“最佳版本”选择 -开发人员**不指定任何版本**的库,或仅指定一个**主要版本**是非常常见的。然后,解释器将尝试下载符合这些要求的**最新版本**。\ -如果库是一个**已知的外部库**(如python的`requests`),攻击者**无法做太多**,因为他无法创建一个名为`requests`的库(除非他是原作者)。\ -然而,如果库是**内部的**,如本例中的`requests-company`,如果**库仓库**允许**检查新版本也来自外部**,它将搜索一个公开可用的更新版本。\ -因此,如果一个**攻击者知道**公司正在使用`requests-company`库的**版本1.0.1**(允许小版本更新)。他可以**发布**库`requests-company`的**版本1.0.2**,而公司将**使用该库而不是**内部库。 +开发人员经常不固定版本或允许广泛范围。当解析器配置了内部和公共索引时,它可能会选择最新版本,而不考虑来源。对于内部名称如 `requests-company`,如果内部索引有 `1.0.1`,但攻击者在公共注册表中发布了 `1.0.2`,并且您的解析器考虑了两者,则公共包可能会胜出。 -## AWS修复 +## AWS 修复 -此漏洞在AWS的**CodeArtifact**中被发现(阅读[**此博客文章中的详细信息**](https://zego.engineering/dependency-confusion-in-aws-codeartifact-86b9ff68963d))。\ -AWS通过允许指定库是内部还是外部来修复此问题,以避免从外部仓库下载内部依赖。 +此漏洞在 AWS CodeArtifact 中被发现(在此博客文章中阅读详细信息)。AWS 添加了控制措施,以将依赖项/源标记为内部或外部,以便客户端不会从上游公共注册表获取“内部”名称。 ## 查找易受攻击的库 -在[**关于依赖混淆的原始帖子**](https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610)中,作者搜索了数千个暴露的package.json文件,这些文件包含JavaScript项目的依赖项。 +在关于依赖混淆的原始帖子中,作者查找了数千个暴露的清单(例如,`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 +``` + + + + + + + + + + + + + + + + + +``` +### Java (Maven/Gradle) + +Maven settings.xml (将所有内容镜像到内部;通过 Enforcer 禁止 POM 中的临时仓库): +``` + + + +internal-mirror +* +https://maven.corp.example/repository/group + + + +``` +添加 Enforcer 以禁止在 POM 中声明的仓库并强制使用您的镜像: +``` + +org.apache.maven.plugins +maven-enforcer-plugin +3.6.1 + + +enforce-no-repositories +enforce + + + + + + + + +``` +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://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610) - [https://zego.engineering/dependency-confusion-in-aws-codeartifact-86b9ff68963d](https://zego.engineering/dependency-confusion-in-aws-codeartifact-86b9ff68963d) +- [https://learn.microsoft.com/en-us/nuget/consume-packages/package-source-mapping](https://learn.microsoft.com/en-us/nuget/consume-packages/package-source-mapping) +- [https://yarnpkg.com/configuration/yarnrc/](https://yarnpkg.com/configuration/yarnrc/) {{#include ../banners/hacktricks-training.md}}