本文机翻自:sonarsource
介绍
技术细节
在本节中,我们将介绍这两个错误的技术特性,描述它们的根本原因以及如何在实际场景中利用它们。我们在本地虚拟机上执行了所有测试以避免破坏官方 PEAR 实例,并在提交f3333c2时使用了官方 Git 存储库。
pear.php.net背后的源代码可以在 GitHub 上的一个名为pearweb的项目中找到。我们的发现影响了 1.32 之前的所有pearweb实例,维护者在该版本中修复了我们发现的漏洞。
该软件的作用是在包的名称(例如Console_Getopt)和下载包的绝对 URL(例如http://download.pear.php.net/package/Console_Getopt-1.4.3 .tgz)。它的妥协将允许更改此关联并强制包管理器从攻击者控制下的非预期来源下载包。
初始立足点:密码重置期间的弱熵
pearweb实例不允许自行注册:账户保留给愿意提议包含在官方 PEAR 存储库中的软件包的开发人员。请求账户可以使用请求账户表单完成,请求者必须提供有关其身份和他们想要分发的项目的信息。然后由 PEAR 管理员手动验证请求。
这是一个减少滥用和最小化服务攻击面的有趣选择:不包括错误跟踪器,唯一没有账户可用的“有趣”功能是此账户请求表单、身份验证和密码重置功能。
在 SonarCloud 上扫描此项目后,我们的引擎在名为 resetPassword() 的方法中识别出一个安全热点:
此代码生成一个随机值,使用 MD5 对其进行哈希处理,然后将其与密码重置所需的其他详细信息一起插入数据库。在这里使用 MD5 不是问题,只要散列值足够强且唯一。
SonarCloud 规则描述中详细解释了该问题:出于安全敏感原因,不应使用mt_rand() 。让我们回顾一下连接在一起的值,然后用md5()散列:
-
mt_rand(4,13) : 4到13之间的整数(包括边界);
-
$user:要重置的账户的用户名,攻击者知道和控制;
-
time() : 当前时间戳;
-
$pass1:攻击者知道和控制的新密码。
从攻击者的角度来看,最终值仅基于两个未知数,即mt_rand()和time()的输出:第一个不能产生很多值 (10),第二个可以很容易地近似为攻击者。此外,pear.php.net的 HTTP 服务器在其响应中添加了一个 Date 标头,将其缩小到只有几个值 (< 5)。
我们可以得出结论,攻击者可以在不到 50 次尝试中发现有效的密码重置令牌,我们开发了一个脚本来利用此弱点并确认其影响:这是介绍视频的第一步。
对于轶事,这个错误是在 2007 年 3 月首次实现此功能时引入的。
通过对现有开发人员或管理员账户使用此漏洞,攻击者可以在其中包含恶意代码后发布现有软件包的新版本。每当有人从 PEAR 获取这些包时,它就会自动下载并执行。
获得持久性:Archive_Tar 中的 CVE-2020-36193
在找到访问保留给已批准开发人员的功能的方法后,威胁参与者可能希望在服务器上获得远程代码执行。这样的发现将赋予他们更多的操作能力:即使前面提到的错误最终得到修复,后门将允许保持对服务器的持久访问并继续更改软件包版本。它还可以帮助他们通过修改访问日志来隐藏他们的踪迹。
鉴别
通过第一个错误获得的初始访问权限将攻击面扩展到没有账户就无法访问的新功能,并且也可能不太安全。
在我们的测试虚拟机上部署pearweb时,我们注意到它在旧版本(1.4.7,而最后一个是 1.4.14)中提取了依赖Archive_Tar :
root@pearweb:/var/www/html/pearweb# pear list
Installed packages, channel pear.php.net:
=========================================
Package Version State
Archive_Tar 1.4.7 stable
查看这个包的变更日志条目,我们可以注意到,在Archive_Tar 1.4.12 之前,可以创建一个指向提取目录之外的绝对路径的符号链接;此错误被跟踪为 CVE-2020-36193。
该错误类非常强大,因为它可以允许在 HTTP 服务器服务的目录中编写 PHP 文件,最终导致任意代码执行。
该库用于提取临时目录中的包内容,以使用phpDocumentor处理它们,然后发布生成的文件:
cron/apidoc-queue.php
$query = "SELECT filename FROM apidoc_queue WHERE finished = '0000-00-00 00:00:00'";
$rows = $dbh->getCol($query);
foreach ($rows as $filename) {
$info = $pkg_handler->infoFromTgzFile($filename);
$tar = new Archive_Tar($filename);
// [...]
/* Extract files into temporary directory */
$tmpdir = PEAR_TMPDIR . "/apidoc/" . $name;
// [...]
$tar->extract($tmpdir);
此代码使用 cron 定期触发,并且每次发布新版本的包时都会将新记录添加到表文件名中:由于获得了初始访问权限,因此攻击者可以访问Archive_Tar::extract()此调用我们提出的第一个错误。
开发
要了解此漏洞背后的技术细节,需要一些有关 Tar 档案的背景知识。归档文件按顺序存储,每个条目都以 512 字节标题为前缀,其内容与 512 字节对齐。条目的结束用两条 512 字节的空记录来表示。文件模式、所有者和组数字标识符以及文件大小等字段使用 ASCII 数字存储为八进制数。
这种归档格式支持将多种“对象”写入磁盘,其中包括符号链接:根据 CVE 描述,我们可以假设 bug 在于Archive_Tar提取此类条目的实现。在源代码中很容易找到它的实现:在[1]我们匹配任何类型为“符号链接”的条目,在[2]删除目标(标题条目文件名),然后最后在[3 ] 创建链接] :
存档/Tar.php
elseif ($v_header['typeflag'] == "2") { // [1]
if (@file_exists($v_header['filename'])) {
@unlink($v_header['filename']); // [2]
}
if (!@symlink($v_header['link'], $v_header['filename'])) { // [3]
$this->_error(
'Unable to extract symbolic link {'
. $v_header['filename'] . '}'
);
return false;
}
与$v_header['link']不同, $v_header['filename']预先使用_maliciousFilename()进行验证,以确保不存在目录遍历字符和危险的方案包装器:
存档/Tar.php
private function _maliciousFilename($file)
{
if (strpos($file, 'phar://') === 0) {
return true;
}
if (strpos($file, '../') !== false || strpos($file, '..\') !== false) {
return true;
}
return false;
}
还应该提到的是,通过始终以目标文件夹 ( $p_path ) 为前缀,绝对路径的提取是安全的:
存档/Tar.php
if (($p_path != './') && ($p_path != '/')) {
while (substr($p_path, -1) == '/') {
$p_path = substr($p_path, 0, strlen($p_path) - 1);
}
if (substr($v_header['filename'], 0, 1) == '/') {
$v_header['filename'] = $p_path . $v_header['filename'];
} else {
$v_header['filename'] = $p_path . '/' . $v_header['filename'];
}
}
正如 CVE 描述所建议的,没有在符号链接的目标上执行验证。它可以通过多种方式被利用,其中:
-
phar://方案包装器被阻止,但没有其他值,例如file ://甚至PHAR://:这些错误是 CVE-2020-28948 和 CVE-2020-28949,都在 Archive_Tar 1.4.11 中修复;
-
创建一个目标在当前目录之外的符号链接。
我们可以创建一个指向提取目录之外的文件夹的新链接并向其中写入文件,或者创建两个具有相同名称的条目(这种格式允许!),第一个是符号链接,第二个是符号链接要写的内容。
我们可以通过将任意内容写入/var/www/html/pearweb/public_html/evil.php来确认此漏洞的可利用性,证明攻击者可以在服务器上执行任意代码。这是概念验证视频的第二步。
修补
维护人员于 8 月 4 日首次发布了第一个补丁,其中他们引入了一种安全方法来在密码重置功能中生成伪随机字节。
由于 PHP 在引用不存在的变量并将它们与默认值NULL相关联时不会引发致命错误,因此该代码有一个可利用的细微缺陷。
在[1]处,将由 16 个随机字节组成的字符串分配给$random_bytes ,而在[2]处调用md5( $rand_bytes ) :第二个变量不存在($rand om _bytes与$rand_bytes),此操作将总是导致空字符串的 MD5 散列(d41d8cd98f00b204e9800998ecf8427e)。
--- a/include/users/passwordmanage.php
+++ b/include/users/passwordmanage.php
@@ -55,7 +55,12 @@ function resetPassword($user, $pass1, $pass2)
{
require_once 'Damblan/Mailer.php';
$errors = array();
- $salt = md5(mt_rand(4,13) . $user . time() . $pass1);
+ // [1]
+ $random_bytes = openssl_random_pseudo_bytes(16, $strong);
+ if ($random_bytes === false || $strong === false) {
+ $errors[] = "Could not generate a safe password token";
+ return $errors;
+ }
+ // [2]
+ $salt = md5($rand_bytes):
PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
$this->_dbh->query('DELETE FROM lostpassword WHERE handle=?', array($user));
$e = $this->_dbh->query('INSERT INTO lostpassword
@@ -91,4 +96,4 @@ function resetPassword($user, $pass1, $pass2)
}
return $errors;
}
我们将这个错字通知了维护人员,之后他们迅速修复了它。他们还升级了正在使用的Archive_Tar版本,防止了我们提出的第二个漏洞。
时间线
日期 | 行动 |
---|---|
2021-07-30 | 我们将所有问题报告给 PEAR 的积极维护者。 |
2021-08-03 | 维护人员确认问题并开始处理补丁;几天后,补丁在 GitHub 上发布。 |
2021-09 - 2022-03 | 我们会定期要求更新,以确保将补丁部署在生产实例上。 |
2022-05-13 | 补丁部署在生产环境中。 |
2022-05-25 | 本文的漏洞在 Insomni'hack 上公开展示。 |
概括
在本文中,我们展示了两个代码漏洞,这些漏洞可能已被利用来对 PEAR 生态系统执行供应链攻击,并危及依赖它的开发人员和公司。这些漏洞已经存在了十多年,并且很难识别和利用,这引发了人们对依赖它的公司缺乏安全贡献的质疑。
我们还建议检查您对 PEAR 的使用并考虑迁移到 Composer,其中贡献者社区更加活跃,并且可以使用相同的软件包。
原文始发于微信公众号(祺印说信安):PHP PEAR 15年漏洞可导致供应链攻击
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论