返回 2026-05-05
🔒 安全

包管理器中的常见 CWE 安全弱点分析Package Manager CWEs

nesbitt.io·2026-05-04

本文系统梳理了各类包管理器(如 npm、pip、apt)中反复出现的通用软件缺陷枚举(CWE)类别。作者识别出诸如路径遍历、命令注入、依赖混淆和权限提升等高频漏洞模式,并指出这些问题往往源于缺乏严格的输入验证和沙箱机制。研究强调需建立统一的包管理安全基线,以降低供应链攻击风险。文中还建议采用静态分析与动态监控结合的方式加强防护。

Andrew Nesbitt

我查阅了所有针对包管理器本身提交的公开CVE和安全公告,包括客户端和注册中心:语言包管理器、系统包管理器、自建注册服务器等。

同样的十几种故障模式在各类工具中反复独立出现,往往相隔数年,因为构建包管理器的团队(第19个)并不总是了解前18个版本的问题所在。如果你正在开发或维护这类系统,这就是你需要关注的清单。我刻意没有按工具分类,因为这些漏洞大多被快速修复,部分由项目自身发现,比起哪个项目触发了哪个漏洞,更重要的是这些漏洞所体现的模式。

需要说明一点:CVE数量反映的是已被报告并分配编号的漏洞,并不代表实际风险水平。有些项目对每个ANSI转义序列都申请CVE,而另一些则在补丁版本中通过更新日志记录修复内容。真正造成现实世界攻击的设计级缺陷(如安装脚本以用户权限运行、信任关系随维护者变更传递、先来先得命名空间机制)之所以不在本文讨论范围内,正是因为它们通常不会获得CVE编号——这将是另一篇文章的主题。

客户端

CWE-22(路径遍历),其变种CWE-59(符号链接跟随)。这是数据集中出现频率最高的原始条目,且已持续存在二十年。当软件包归档文件中包含带有../的路径、指向提取根目录外的符号链接,或Windows风格的反斜杠分隔符未被路径净化代码识别时,安装程序会按照作者意图将文件写入任意位置。

2018年Zip Slip研究首次同时发现了这一漏洞在大多数生态系统中的普遍性,但此前早已逐个出现(我在语言包管理器中发现最早的是CVE-2007-0469),此后也持续发生(如CVE-2026-34591、CVE-2026-35206)。修复..情况无法解决符号链接问题,为tar修复符号链接不能解决zip的问题,处理正斜杠的方案在Windows的反斜杠上同样失效。多个工具在此类别中有三到四个CVE,因为每次修复都是局部的。

该模式不仅限于归档文件本身。清单文件中的bin字段指向../../../usr/local/bin/something(CVE-2019-16775),下载文件的Content-Disposition头部包含../的文件名(CVE-2019-20916、CVE-2019-9686),图表名称或版本字符串中包含路径段(CVE-2023-35946)。任何来自软件包的字符串最终成为文件系统路径的地方都可能存在此类问题。

如果你正在构建此类系统:请将文件解压到指定目录,并拒绝创建任何解析后路径不在此目录下的文件。在检查路径前先解析符号链接,对待目标操作系统视为分隔符的所有字符都要同等处理,对所有会变成路径的字段都应用相同检查,而不仅仅是归档条目。

向git、hg、svn等版本控制系统注入参数

CWE-88(参数注入)。几乎所有包管理器都能从VCS URL安装,而几乎所有实现都曾将用户可控字符串传递给git clone或hg clone等命令,导致其被解释为选项而非参数。例如ref为--upload-pack=payload,URL以破折号开头,分支名称包含shell元字符等情况。

典型案例包括:CVE-2021-29472、CVE-2022-36069、CVE-2021-43809、CVE-2023-5752、CVE-2022-24440。某个工具在git、hg和Perforce驱动方面就累计出现了六个独立的CVE。

修复方法是使用 -- 分隔符并允许传递特定选项的白名单,但问题反复出现是因为每个 VCS 后端都是独立编写的,三年前从 git 驱动学到的经验并未传递给 p4 驱动。

相关但不同的漏洞簇是编译器标志注入:在构建时将 -fplugin= 或任意的 LDFLAGS 注入 C 编译器。这主要是 Go 语言的典型问题(CVE-2018-6574、CVE-2023-29404、CVE-2023-39323),因为 cgo 的工作方式所致,但其本质相同:不可信字符串被传递给将某些字符串视为指令的子进程。

完整性检查失败时仍放行

CWE-345(数据真实性验证不足)和 CWE-347(密码签名验证不当)。缺失签名文件导致跳过验证,或校验器返回错误却被当作成功处理。其他情况下,哈希值在缓存入队时被检查,但在出队时未再验证,或依赖项验证已启用,但某条代码路径绕过了它。

CVE-2016-1252 是典型例子:一个 clearsigned 文件解析器在特定条件下会让未签名的内容通过,仿佛已被签名。CVE-2022-31156 在签名检查无法运行时静默跳过验证。CVE-2012-6088 将无法解析的签名视为有效签名。CVE-2026-35205 在 provenance 文件不存在时仍通过插件验证。

早期版本的情况更简单:没有 TLS,或 TLS 不验证证书,或跟随 HTTPS 到 HTTP 的重定向(CVE-2012-2125、CVE-2013-1629、CVE-2013-0253)。现在大部分已修复,尽管 CVE-2022-46176(git index 克隆时不验证 SSH 主机密钥)表明类似漏洞仍可能存在于较少审查的传输方式中。

如果你有一个验证步骤,请编写测试用例,分别测试签名缺失、格式错误、以及内容不同但形式有效的签名,确保这三种情况均拒绝安装。

凭证发送到错误的位置

CWE-522(凭证保护不足)。一个限定于某个注册表的认证令牌被发送到另一个注册表,通常通过重定向、镜像或托管在其他地方的依赖项实现。CVE-2016-3956 在每个请求中都发送 bearer token,无论目标主机是谁。CVE-2019-15052 跟随重定向并将凭据重新发送至新主机。CVE-2021-32690 将仓库凭据发送给图表依赖项所指向的任何域名。CVE-2021-22568 将官方注册表的 OAuth token 发送给任何第三方服务器,只要你发布到该服务器。

此类问题的另一半是凭证意外出现在日志或发布的制品中:密码嵌入 URL 并写入构建日志(CVE-2020-15095)、调试级别输出签名口令(CVE-2020-13165)、工作区忽略文件未被遵守,导致密钥被打包进发布的 tarball(CVE-2022-29244)。

凭证应绑定来源并在跨源重定向时剥离,就像浏览器一样。任何构建可发布制品的工具都应包含测试,确保 .env 不会意外包含在内。

选择错误的来源

解析器层的 CWE-427(不受控的搜索路径元素),也被归类为 CWE-829(从不信任控制域引入功能)。这就是“依赖混淆”家族。私有包名在公共注册表中也存在,解析器同时查询两者,“最高版本胜出”机制导致公共版本被安装。或者清单中的次要源被允许满足任意依赖,而不仅限于其原本添加的用途。

早在它被命名之前,这种漏洞就已针对各个工具报告过:CVE-2013-0334 和 CVE-2016-7954 是 Alex Birsan 在 2021 年使其广为人知的同一漏洞;CVE-2020-36327 和 CVE-2018-20225 是 2021 年之后的报告,后者被维护者以文档记录的行为为由提出争议;CVE-2022-21668 则是更严重的变种,其中 requirements 文件注释中的 --index-url 会将后续所有安装重定向到攻击者的索引。

还有一种不太明显的版本,当某个配置源出错或无法访问时,解析器会回退到下一个配置源(CVE-2026-22865);另一种情况是将非发行版内容误识别为发行版(CVE-2022-23773,分支名称看起来像版本标签)。

每个依赖项应仅从一个源解析,且该源应在锁文件中固定。若某源不可达,则应失败而非尝试下一个源。

本地文件和目录作为可信配置

再次涉及 CWE-426(不受信任的搜索路径)和 CWE-427。在不受信任的目录(如新克隆的仓库、下载的 tarball、挂载的卷)中运行包管理器,并让它从该目录中拾取可执行文件、配置文件或搜索路径。CVE-2021-4435 和 CVE-2021-3115 会在当前目录中运行找到的二进制文件;CVE-2023-39320 允许模块文件中的 toolchain 指令指向模块内的二进制文件;CVE-2024-24821 会从项目的 vendor/ 目录加载 PHP 文件到包管理器自身的进程中。

这与“代码在你预期之前就执行”的问题重叠。用户通常认为 install 会运行代码,而 status、lock 或 audit 不会,而这些 CVE 恰恰出现在模型错误的地方。

其他用户可写入的共享文件系统位置

CWE-377(不安全的临时文件)和 CWE-276(错误的默认权限)。在 /tmp 下使用可预测路径、全局可写的缓存或安装目录、解压文件时使用归档文件的 mode bits 而非用户的 umask。另一个本地用户在您到达前放置文件,或在您放置后修改它。

CVE-2019-3881、CVE-2021-29428 和 CVE-2023-38497 是语言包管理器的例子。系统包管理器在此问题上历史更长,因为它们以 root 身份运行:在“安装程序创建文件”和“安装程序设置其权限”之间进行符号链接或硬链接交换会导致权限提升,且同一种工具可在十年内累积多个此类问题(CVE-2017-7500、CVE-2021-35937、CVE-2021-35939),因为每次修复都被发现是不完整的。

使用 mkdtemp 提供的进程专属临时目录,在创建时而非之后设置权限,在系统包管理器方面使用与 fd 相关的操作而非基于路径的操作,以确保您检查的内容正是您修改的内容。

清单文件的不安全反序列化

CWE-502(对不受信任数据的序列化解反序列化)。使用 YAML 或编组库解析包元数据,这些库可能实例化任意对象。CVE-2017-0903 是著名的案例:gemspec 是 YAML 格式,YAML 加载器是不安全变体,精心构造的 gem 在签名检查运行前即可实现解析时的代码执行;CVE-2025-32798 与之类似,但配方选择器被评估为 Python 代码。

XML 孪生漏洞是 CWE-611(XML 外部实体引用)。Java 生态系统的清单文件采用 XML 格式,平台默认解析器会解析外部实体,而 POM 或 ivy.xml 中的 <!DOCTYPE> 声明会从执行解析的机器上读取本地文件。CVE-2022-46751 和 CVE-2023-42445 分别相隔一年出现在两个工具中,它们都因使用默认开启的解析器处理同一种文件格式而触发了相同的漏洞。

本类别下的两类漏洞数量虽少,但严重性极高,因为它们仅凭元数据就能实现代码执行或文件泄露,甚至在用户尚未同意安装任何内容之前就已发生。请始终使用安全的加载方式调用你所用解析器的变体,如果是 XML 格式,务必关闭 DTD 和外部实体的解析。

精心构造的包导致资源耗尽

CWE-400(不受控的资源消耗)和 CWE-1333(低效的正则表达式复杂度)。例如,一个压缩归档文件解压后占用整个磁盘空间(CVE-2022-36114),或版本字符串、git URL 在正则表达式中引发指数级回溯(CVE-2013-4287、CVE-2025-8262)。CVE-2025-55199 是一个 JSON Schema,其 $ref 链过长导致内存耗尽;CVE-2018-1000075 则是一个 tar 文件头包含负值大小,导致无限循环。

这类问题在开发者机器上通常只是小麻烦,但在共享 CI 运行器或任何自动处理包的系统中更具威胁。建议限制解压文件大小和递归深度,并对处理包元数据的正则表达式进行审查。

元数据中的终端转义序列

CWE-150(未正确中和转义序列)。注册表返回的包名、描述或错误信息会被直接输出到终端,包括 ANSI 转义序列。这使得恶意包可以重写之前的输出、隐藏实际安装的内容,或在某些终端模拟器中造成更严重的后果。目前至少存在九个 CVE(如 CVE-2017-0899、CVE-2021-21303、CVE-2025-67746),此外还有一个变种:构建时序报告将攻击者控制的特性名称以 HTML 形式输出而未转义(CVE-2023-40030)。

在将来自包的数据输出到标准输出前,应剥离其中的控制字符。

锁文件和缓存未能真正固定依赖

无明确的 CWE 编号,通常归类于 CWE-345。锁文件存在,用户认为安装是可复现的,但某些代码路径却忽略了其约束。例如 CVE-2021-43616 在锁文件与清单不匹配时仍能安装成功;CVE-2025-69263 允许通过冻结的锁文件引入远程动态依赖;CVE-2019-15608 在缓存写入时检查哈希,但在读取时未再验证。

较新的变种是解析器差异:两个工具,或一个工具与审计工具之间,对同一归档文件内部内容的解释不一致。CVE-2023-37478 是一个 tarball,不同安装器对其解读不同,但哈希值与锁文件一致;CVE-2026-3219 的文件根据读取端的不同被识别为 tar 或 zip 格式。安全扫描器看到的包与实际安装的包并不相同。

构建沙箱逃逸

CWE-693(保护机制失效)。对于少数尝试隔离包构建环境的工具而言,漏洞往往出现在隔离机制本身。例如通过 Unix 套接字在构建间传递文件描述符(CVE-2024-27297)、跨平台权限降级失败(CVE-2025-53819)、内置获取器在构建沙箱之外运行等。Homebrew 审计也发现了多个此类沙箱逃逸漏洞。

该类别规模较小,主要是因为大多数包管理器根本不进行构建沙箱隔离。这反而将风险从 CVE 记录转移到了设计层面——虽然影响范围更大,但缺乏编号标识。

归档解析器中的内存损坏

CWE-787(越界写入)及其同类漏洞。主要影响使用 C 语言编写并自带 ar、tar、cpio 或头部解析器的工具。长度字段上的整数溢出、魔数字符串的 off-by-one 错误,以及错误路径中的格式化字符串漏洞(如 CVE-2020-27350、CVE-2014-8118、CVE-2015-0860)。整个系统包管理器中约有十余个此类问题。

除了显而易见的一点外,几乎无话可说:如果你能使用内存安全语言或为归档层选用经过充分模糊测试的库,那就这么做。

注册表

在注册表侧,披露记录呈现明显失衡状态。自托管注册表服务器(Nexus、Artifactory、Harbor、Verdaccio、NuGet Gallery)是交付的软件,因此其漏洞会获得 CVE ID;而大型托管注册表作为服务,其漏洞通常最多只在博客上提及。此处我参考了事件复盘报告、审计报告及 HackerOne 披露内容,并结合 CVE 记录进行分析。

发布或替换他人软件包

CWE-285(权限不当)和 CWE-863(权限错误)。最关键的是注册表端的漏洞——它可直接导致供应链被入侵,无需触碰任何账户。CVE-2022-29176 是一个名称解析边界情况,允许你删除并替换任何包含连字符名称的软件包;CVE-2022-29218 则通过利用上传顺序,可在 CDN 中覆盖他人发布的平台特定版本。CVE-2024-38368 中一次迁移操作遗留了一个长达十年的孤儿所有权 API,任何人都能认领 1,800 个软件包。某注册表存在微服务间授权间隙,允许任何人发布任意软件包的新版本,该问题已被报告并修复,但未分配 CVE。

自托管注册表也存在类似问题:角色检查验证的是错误对象——维护者可编辑管理员专属的项目元数据(CVE-2024-22278),自注册接口接受请求体中的 admin: true 字段(CVE-2019-16097),低权限用户可访问赋予管理员权限的端点(CVE-2024-4142)。

发布、撤销(yank)和转移所有权等端点是威胁建模的重点。所有“此操作作用于哪个软件包”的输入均由攻击者控制。

通过恢复路径获取账户控制权

CWE-287(认证不当),但实际场景中极少源于登录表单本身的漏洞。更常见的情况是:维护者的邮箱域名过期,被他人注册,随后请求密码重置并成功发布。至少三大主流注册表均发生过此类事件(ctx 事件有详细记录),npm 自身模型中也将其列为开放威胁。凭证填充变体同样有效:2017 年扫描发现,重用密码的用户约占某注册表下载权重 14% 的账户具备发布权限,2023 年某事件亦通过此方式劫持了十四个软件包。

该类别下的真实代码漏洞多与令牌管理有关。某注册表曾使用非加密随机数生成令牌且未哈希存储(2020 crates.io 公告);CVE-2019-9733 允许通过 X-Forwarded-For: 127.0.0.1 标头绕过仅限本地访问的管理员账户检查;CVE-2024-38367 是一个会话验证链接,攻击者可通过受害者邮件扫描器零接触触发点击;CVE-2024-22244 则是宽松的 OAuth redirect_uri 检查,会将授权码发送至攻击者在链接中指定的任意主机。

注册机构有能力在攻击者之前发现域名过期的情况,目前已有多个机构实现了这一点。强制实施双因素认证(2FA)发布机制可阻止大部分其他情况,前提是绕过2FA的API令牌确实具有相应的权限范围。

渲染软件包页面中的存储型XSS漏洞

CWE-79(跨站脚本攻击)。注册机构会渲染来自软件包上传的README文件、描述内容、主页链接或个人资料字段,而清理器未能正确处理某些内容。具体包括:Markdown自动链接中的javascript: URI(CVE-2024-37304)、渲染描述时的HTML属性处理问题(CVE-2024-47604),以及README内容在渲染前未经过适当清理(GHSA-78j5-gcmf-vqc8)。自托管注册机构普遍存在此类问题。

与一般网页XSS相比,注册机构的此类漏洞风险更高,因为受害者已登录为拥有发布权限的用户,且他们正在查看的页面是由希望获得这些权限的人提供的。因此,必须使用真正的清理器并配合假设清理器最终可能遗漏某些内容的CSP策略来处理由软件包提供的真实内容。

通过软件包元数据实现服务端代码执行

CWE-94(代码注入),通常与客户端存在相同的漏洞。注册机构解析清单和克隆仓库时使用的代码与客户端相同,因此gem库中的YAML反序列化漏洞会导致注册机构和开发机器上的远程代码执行(RCE),Composer的hg驱动中的参数注入漏洞也会在packagist.org和本地同时造成命令执行。CVE-2024-38366描述了注册机构在邮箱验证过程中执行shell命令的情况。2021年CocoaPods trunk的RCE漏洞同样是git ls-remote参数注入,但发生在服务端。

如果您同时维护客户端和服务端注册机构,那么上述每个客户端解析或VCS漏洞都值得在服务端也进行检查。注册机构是更有价值的目标,并且会处理相同的不可信输入。

通过仓库URL和webhook实现的SSRF

CWE-918(服务端请求伪造)。注册机构会获取用户提供的URL,而该URL可能指向元数据服务、内部管理端口或其他租户的端点。例如:允许项目管理员进行内网端口扫描的"测试webhook"功能(CVE-2020-13788)、服务器代表用户获取远程仓库配置(CVE-2022-27907),以及上传的XML配置文件中的XXE漏洞可访问内部主机(CVE-2020-29436)。自托管注册机构在此类问题上尤为集中,因为它们通常运行在企业网络内部,使得SSRF能够访问到有价值的目标。

从用户提供的URL发起的出站请求需要白名单机制、DNS重绑定检查,并禁止访问私有地址范围。

管理端点和设置接口的IDOR

CWE-639(不安全的直接对象引用)。请求指定要操作的项目、软件包、webhook或机器人账户,服务器仅检查您是否有权执行该操作,但未验证您是否拥有该对象的所有权。某自托管注册机构曾一次性报告了五个此类漏洞,涉及webhook策略、机器人账户、标签不可变性规则、保留策略和作业日志(CVE-2022-31666至CVE-2022-31671)。其他案例还包括向任何已认证用户泄露完整的仓库列表(CVE-2021-46270)或系统代理配置(CVE-2024-3505)。

这些问题主要集中在多租户自托管注册机构中,其中一台服务器托管多个组织的软件包。每个接收对象ID的处理程序都需要检查所有权,包括只读操作。

令牌授予超出其声明的权限

再次出现 CWE-863。一个“仅上传”的 API 令牌,若以头部形式发送则等同于会话令牌(PyPI 2020 年披露)。只读密钥可被兑换为全范围 OAuth 令牌(CVE-2026-21621)。自动化令牌按设计绕过双因素认证,且无法限定到单个包。

发布令牌是攻击者从 CI 中提取的目标,因此令牌上显示的权限范围必须与服务器实际强制执行的范围一致,且不应存在从窄范围令牌升级到宽范围令牌的路径。

注册表与客户之间存在分歧

无明确的 CWE 归类。注册表存储并展示一组元数据,而客户端根据另一组元数据进行安装,攻击者可操纵两者不一致。2023 年 npm 清单混淆报告即典型案例:发布 API 接受独立于 tarball 内 package.json 的清单,导致网站、审计工具和 npm install 各自看到的依赖列表不同。Go proxy 变种问题在于其具有时间性而非空间性:代理缓存首次看到的模块路径版本,并在源仓库清理后仍继续提供该旧版本。

关键在于确保每个包的内容只有一个权威来源,并保证所有使用者都从此源头读取信息。

在约两百条通告中,这大致涵盖了全部漏洞模式。客户端有十二种模式,注册表有八种,调查中的几乎所有工具至少涉及其中一半。包管理器项目为自身安全所能做的最便宜的事,就是订阅五六个其他项目的安全公告——因为本月有人提交的某个 tar 解压器漏洞,很可能就是你所用工具的相同漏洞。

需要完整排版与评论请前往来源站点阅读。