对 PHP 的新供应链攻击

admin 2022年10月7日20:34:30评论27 views字数 7475阅读24分55秒阅读模式

保护开发人员工具:对 PHP 的新供应链攻击

对 PHP 的新供应链攻击

关键事实

Sonar 发现并负责任地披露了 PHP 供应链的核心组件 Packagist 中的一个关键漏洞,以帮助保护开发人员工具。

此漏洞允许获得对包装工的控制权。PHP 包管理器 Composer 使用它来确定和下载开发人员在其项目中包含的软件依赖项。

几乎所有运行PHP代码的组织都在使用Composer,它每月提供20亿个软件包。这些请求中有超过一亿个可能被劫持,以分发恶意依赖项并危及数百万台服务器。

这项新研究是在同一服务中发现类似结果一年后发布的,该发现记录在PHP供应链攻击Onxior中。

受影响服务的维护人员在数小时内修复了此漏洞。

介绍

供应链攻击是当今开发组织的热门话题。去年,在有史以来最大的软件供应链攻击中,一个后门感染了18,000名SolarWinds客户。今年早些时候,一名安全研究人员使用一种新的供应链攻击技术,能够破坏苹果、微软、PayPal和其他科技巨头。

这些攻击所利用的底层设计是,所有现代软件都是在其他第三方软件组件之上构建的,通常无法清楚地看到所有下载的软件包。虽然重用许多组件可以加快开发过程,但感染供应链是一个非常有效和微妙的攻击媒介,可以立即损害许多组织。

虽然供应链可以采取不同的形式,但其中之一的影响要大得多:通过访问分发这些第三方软件组件的服务器,威胁参与者可以改变它们,以便在用户的系统中获得立足点。

在我们首次发布有关 PHP 供应链中的关键漏洞的一年后,Sonar 研发团队在类似组件中发现了一个新的关键漏洞。它允许控制服务器分发有关现有PHP软件包的信息,并最终损害使用它们的每个组织。

在本出版物中,我们将在最大的 PHP 包管理器 Composer 及其官方包存储库 Packagist 中介绍我们的发现。我们解释了发现的代码漏洞在理论上是如何工作的,它如何影响Packagist,以及我们如何在测试实例和真实实例上演示它。我们还将研究如何防止这些代码漏洞,以及维护人员如何修补这个特定的漏洞。

冲击

我们在本出版物中演示的攻击允许我们在运行Packagist官方实例的服务器上执行任意命令。Composer 使用此服务提取与给定包及其依赖项关联的元数据。每个月,大约有 20 亿个软件依赖项通过 Composer 从 Packagist 下载,其中至少有 1 亿个安装需要从 Packagist 获取元数据

这些后端服务的安全性至关重要:它们执行包名称与包管理器应从中下载包的位置之间的关联,因此,破坏它们将允许攻击者在下次根据2021年的数据对 Composer 包进行全新安装或更新时强制用户下载后门软件依赖项。由于 Composer 是 PHP 的标准包管理器,因此大多数开源和商业 PHP 项目都会受到影响。

如果您使用的是默认的官方包装机实例或私有包装机,那么您已经是安全的。我们负责任地披露了我们的发现,维护人员在几个小时内在公共生产实例上修补了它。

如果将 Composer 集成为库并在不受信任的存储库上运行,请至少升级到 Composer 1.10.26、2.2.12 或 2.3.5,以便从 CVE-2022-24828 的安全修补程序中受益。

以前的工作

现在,让我们深入研究这一新发现的技术细节,看看我们可以学到什么。正如您将看到的,在PHP供应链攻击Onxior中记录的内容之间存在直接联系:将首先总结一年前所做的工作,展示我们的一种方法如何导致死胡同,最后看看我们如何重用我们去年引入的相同开发技术。

CVE-2021-29472 的发现

我们之前在 CVE-2021-29472 方面的工作为我们提供了有关有趣攻击面的见解。尽管我们查看了修复 CVE-2021-29472 的补丁,但我们可能遗漏了一些东西,并且重新关注它们是相关的。

我们确定的漏洞发生在VcsDriver子类的实现中:每个受支持的版本控制系统(因此得名)都存在一个驱动程序,如Git,Mercurial,Subversion等。它们的作用是与这些工具创建的代码存储库进行交互,而无需重新实现相关的必要代码;相反,作曲家将它们作为外部命令调用。

调用系统命令的代码通常容易出现两类主要漏洞:

命令注入:攻击者可以注入稍后由shell解释的命令替换序列,以强制执行其他任意命令

参数注入:攻击者可以向调用的命令添加额外的参数,希望以危险的方式影响其行为

命令注入?参数注入?

为了更好地理解这些概念,让我们从八月底在BARBHACK上发表的演讲中看几张幻灯片。

在命令注入 bug 的情况下,攻击者控制的值根本没有转义,$() 中的命令首先由 shell 执行,其输出在第二个命令中使用:

对 PHP 的新供应链攻击

假设攻击者控制的值被转义函数正确地用单引号括起来。在这种情况下,shell 将忽略命令替换,并将其视为字符串文本中的常规字符:

对 PHP 的新供应链攻击

但是,调用的命令的参数解析器将把此值解释为操作数,并在以一个或多个短划线 (-h, --help) 为前缀时将其解释为参数:

对 PHP 的新供应链攻击

在此示例中,将显示一条无害的帮助消息,但我们发现了 hg 客户端的一个特定选项,该选项允许在所有情况下执行任意命令。

如您所见,使用转义函数无法防止参数注入漏洞。这可能令人惊讶,因为我们习惯于通过转义或编码特殊字符来中和它们,以防止所谓的注入漏洞(例如,SQL注入)。

在这里,开发人员必须使用一个称为选项结束的特殊选项:作为POSIX规范的一部分,它用于告诉解析其参数的程序将选项与操作数分开。简单来说,位于选项结束序列右侧的任何内容都将被视为操作数:运行 hg 标识-- --帮助不会显示帮助消息。

发现新的漏洞

Packagist 界面显示有关软件包的信息,例如,此处是著名的 Symfony 框架:https://packagist.org/packages/symfony/symfony

对 PHP 的新供应链攻击

导入或更新新包时,将通知异步工作线程。然后,他们将拉取与其关联的整个存储库。此过程的步骤之一是更新此包的主文档页面。

默认情况下,此内容源自名为 README.md 的文件。此文件名可能与其他服务冲突,因此维护者添加了一个选项,用于直接在包的清单中指定此文件名,如 https://getcomposer.org/doc/04-schema.md#readme 中所述。

要获取此文件的内容,请在 [1] 处获取分支的名称,在 [2] 处获取文件名,最后在 [3] 处调用 getFileContents():

包装师/src/软件包/更新程序.php

private function updateReadme(IOInterface $io, Package $package, VcsDriverInterface $driver): void

{

   // [...]

   try {

       // [1]

       $composerInfo = $driver->getComposerInformation($driver->getRootIdentifier()); 

       if (isset($composerInfo['readme']) && is_string($composerInfo['readme'])) {

           // [2]

           $readmeFile = $composerInfo['readme'];

       } else {

           $readmeFile = 'README.md';

       }

       // [...]

       switch ($ext) {

           case '.txt':

               // [3]

               $source = $driver->getFileContent($readmeFile, $driver->getRootIdentifier());

               if (!empty($source)) {

                   $package->setReadme('

' . htmlspecialchars($source) . '

');


               }

               break;

               // [...]

getFileContent() 的目标是允许从给定分支、标记或提交的存储库中读取文件。这是最快的方法,也可能更安全:在执行多个shell命令时,没有错误地跟随指向非预期目的地的符号链接或引入命令注入漏洞的风险。

每个 Vcs 驱动程序都实现此方法的其版本。让我们把重点放在 Git 驱动程序(对于 Git)和 Hg 驱动程序(对于汞合金):

作曲家/src/作曲家/存储库/VC/Git驱动程序.php

public function getFileContent(string $file, string $identifier): ?string

{

   $resource = sprintf('%s:%s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file));

   $this->process->execute(sprintf('git show %s', $resource), $content, $this->repoDir);

   // [...]

}

作曲家/src/作曲家/存储库/风险投资人/Hg司机.php

public function getFileContent(string $file, string $identifier): ?string

{

   $resource = sprintf('hg cat -r %s %s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file));

   $this->process->execute($resource, $content, $this->repoDir);

   // [...]

}

这与我们之前的发现类似,我们可以注入额外的参数。两者都是利用的理想之选,因为分支和文件的名称是通过清单文件完全控制的。 

正在调查 Git 驱动程序

作为提醒,此命令将在 git 显示“<分支>”:“<文件>”时调用。我们不能使用文件名来注入新参数,因此我们必须找到一种方法来创建一个Git分支,其中包含有效负载所需的所有字符,并处理该强制后缀(:'')。

git show支持的所有选项中,只有--output似乎很有希望,因为它允许将当前Git存储库的所有文件的内容写入任意目标。在保护开发人员工具:Git 集成中,我们已经证明,当攻击者可以控制或修改内部文件(如 .git/config)时,Git 存储库的安全性非常脆弱。此文件将是此处选择的目标。

第一步是创建一个分支,其名称中包含我们注入的选项。本应简单的东西似乎被阻止了:

$ git checkout -b --help

fatal: '--help' is not a valid branch name

我们仍然可以找到一种方法来强制它在本地存储库上,并且这个分支将被Git远程接受:

$ echo "ref: refs/heads/--help" > .git/HEAD

$ mv .git/refs/heads/main .git/refs/heads/--help

$ git push origin -- --help

但是,强制后缀成为一个重要的约束。解决这个问题的唯一方法是在例如 foo:README.md 和 .git/配置之间创建一个符号链接。

我们很快发现这条路径是一条死胡同:存储库被克隆为裸露的(请注意下面代码片段中的选项 --mirror),这意味着该目录不会公开存储库中恶意包中的文件。

作曲家/src/作曲家/Util/Git.php

public function syncMirror(string $url, string $dir): bool

{

   // [...]

   $commandCallable = static function ($url) use ($dir): string {

       return sprintf('git clone --mirror -- %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($dir));

   };

   $this->runCommand($commandCallable, $url, $dir, true);

回到HG司机

现在,让我们来看看其他易受攻击的VCS驱动程序。这一次,该命令被调用为 hg cat -r “<分支>”“<文件>”;这是一个比在 Git 驱动程序中更理想的上下文。

上一部分所述,我们可以使用Mercurial的--config选项来覆盖内置命令(例如cat)的行为,并使其执行任意shell脚本。

我们可以根据上述信息制作以下有效负载,其方式与我们对CVE-2021-29472所做的非常相似:

对 PHP 的新供应链攻击

有效载荷可能比您预期的稍微复杂一些。让我们来分解一下:

注入的配置覆盖:这是声明 shell 命令覆盖 Mercurial 的 cat 的额外参数;

有效负载:存储库被克隆为裸存储库,因此我们无法访问文件。使用对 hg cat 的未修改调用,我们可以读取存储库名为 payload.sh 的文件,并将其传送到 shell;

强制后缀:包装师仅处理以 .txt 或 .md 结尾的文件;其他的被丢弃。

攻击者必须按照以下步骤尝试针对 Packagist 利用此漏洞:

在远程资源存储库中创建项目;

将清单放在作曲家.json中,并添加恶意自述文件条目;

使用上述有效负载时,请创建一个名为 payload.sh 的文件以执行所需的操作;
定义

在包装机上导入软件包,并请求更新软件包。

我们在设置的测试实例上执行了以下步骤,并且可以演示在服务器上执行任意命令:

下一步是修改包的定义,使其指向一个意想不到的目的地,并损害使用它们的应用程序;这是我们在Insomni'hack谈话中已经证明的东西,在本文中不会再次提出。

packagist.org,生产实例上的此漏洞的利用也通过非破坏性命令进行了演示。我们立即与维护人员联系,提供了我们尝试的所有技术细节,IP地址等。应该注意的是,维护者没有发现任何先前利用此漏洞的行为。

补丁

CVE-2022-24828

您可能还记得前面的部分中,不可能使用 POSIX 选项结束开关在 GitDriver 中修补注入。Git 引入了一个非标准标志 --端选项,但仅从 Git 2.24 开始支持它,这可能会破坏某些用户的 Composer。

结果,维护者合并了2c40c53,其中包含两个易受攻击的 VcsDriver 类的补丁。首先,GitDriver 通过禁止名称以破折号开头的任何分支来修补:

    public function getFileContent($file, $identifier)

    {

+        if (isset($identifier[0]) && $identifier[0] === '-') {

+            throw new RuntimeException('Invalid git identifier detected. Identifier must not start with a -, given: ' . $identifier);

+        }

+

        $resource = sprintf('%s:%s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file));

        $this->process->execute(sprintf('git show %s', $resource), $content, $this->repoDir);

以类似的方式,HgDriver 现在禁止在分支名称中使用前导斜杠,并引入了选项结束开关来防止文件名的参数注入:

    public function getFileContent($file, $identifier)    {

-        $resource = sprintf('hg cat -r %s %s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file));

+        if (isset($identifier[0]) && $identifier[0] === '-') {

+            throw new RuntimeException('Invalid hg identifier detected. Identifier must not start with a -, given: ' . $identifier);

+        }

+

+        $resource = sprintf('hg cat -r %s -- %s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file));        

$this->process->execute($resource, $content, $this->repoDir);

进一步硬化

Composer 与其他包管理器略有不同,因为它仅使用 Packagist 来获取有关给定包的元数据,并在以后从其他源下载依赖项。他们没有托管软件包,因此集成和实施像sigstore这样的工具变得稍微困难一些。

时间线

日期

行动

2022-04-07

我们向包装工维护者报告此漏洞。

2022-04-07

供应商承认问题并开始处理修补程序。

2022-04-08

packagist.org 的公共实例已进行热修补。

2022-04-13

CVE分配,由Packagist在其博客和新作曲家版本上进行官方通信。未检测到先前利用 CVE-2022-24828 的指示器。

总结

我们演示了如何在 PHP 包管理器 Composer 的后端服务中发现参数注入,并可以成功利用它来破坏任何 PHP 软件依赖项。

这是维护者和漏洞研究人员错过的回顾性简单错误的完美示例,即使两者在合并CVE-2021-29472的安全补丁之前可能都花了几个小时来处理此代码!以清晰的头脑回到旧的错误是一个强大的工具,不容低估。

作者:托马斯·肖切弗因

原文始发于微信公众号(祺印说信安):对 PHP 的新供应链攻击

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年10月7日20:34:30
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   对 PHP 的新供应链攻击https://cn-sec.com/archives/1335325.html

发表评论

匿名网友 填写信息