主页 > 网络知识 > 从反序列化到类型混淆漏洞:记一次ecshop实例利用(2)

从反序列化到类型混淆漏洞:记一次ecshop实例利用(2)

如果我们有一个可控的反序列化入口,目标后端PHP安装了GMP插件(这个插件在原版php中不是默认安装的,但部分打包环境中会自带),如果我们找到一个可控的__wakeup魔术方法,我们就可以修改反序列化前声明的对象属性,并配合场景产生实际的安全问题。

如果目标的php版本在5.6 <= 5.6.11中,我们可以直接使用内置的魔术方法来触发这个漏洞。

var_dump(unserialize('a:2:{i:0;C:3:"GMP":17:{s:4:"1234";a:0:{}}i:1;O:12:"DateInterval":1:{s:1:"y";R:2;}}')); 真实世界案例

在讨论完GMP类型混淆漏洞之后,我们必须要讨论一下这个漏洞在真实场景下的利用方式。

漏洞的发现者Taoguang Chen提交了一个在mybb中的相关利用。

https://hackerone.com/reports/198734

这里我们不继续讨论这个漏洞,而是从头讨论一下在ecshop中的利用方式。

漏洞环境

ecshop 4.0.7

php 5.6.9

反序列化漏洞

首先我们需要找到一个反序列化入口点,这里我们可以全局搜索unserialize,挨个看一下我们可以找到两个可控的反序列化入口。

其中一个是search.php line 45

... { $string = base64_decode(trim($_GET['encode'])); if ($string !== false) { $string = unserialize($string); if ($string !== false) ...

这是一个前台的入口,但可惜的是引入初始化文件在反序列化之后,这也就导致我们没办法找到可以覆盖类变量属性的目标,也就没办法进一步利用。

还有一个是admin/order.php line 229

/* 取得上一个、下一个订单号 */ if (!empty($_COOKIE['ECSCP']['lastfilter'])) { $filter = unserialize(urldecode($_COOKIE['ECSCP']['lastfilter'])); ...

后台的表单页的这个功能就满足我们的要求了,不但可控,还可以用urlencode来绕过ecshop对全局变量的过滤。

这样一来我们就找到了一个可控并且合适的反序列化入口点。

寻找合适的类属性利用链。在寻找利用链之前,我们可以用 get_declared_classes()

来确定在反序列化时,已经声明定义过的类。

在我本地环境下,除了PHP内置类以外我一共找到13个类

[129]=> string(3) "ECS" [130]=> string(9) "ecs_error" [131]=> string(8) "exchange" [132]=> string(9) "cls_mysql" [133]=> string(11) "cls_session" [134]=> string(12) "cls_template" [135]=> string(11) "certificate" [136]=> string(6) "oauth2" [137]=> string(15) "oauth2_response" [138]=> string(14) "oauth2_request" [139]=> string(9) "transport" [140]=> string(6) "matrix" [141]=> string(16) "leancloud_client"

从代码中也可以看到在文件头引入了多个库文件

require(dirname(__FILE__) . '/includes/init.php'); require_once(ROOT_PATH . 'includes/lib_order.php'); require_once(ROOT_PATH . 'includes/lib_goods.php'); require_once(ROOT_PATH . 'includes/cls_matrix.php'); include_once(ROOT_PATH . 'includes/cls_certificate.php'); require('leancloud_push.php');

这里我们主要关注init.php,因为在这个文件中声明了ecshop的大部分通用类。

在逐个看这里面的类变量时,我们可以敏锐的看到一个特殊的变量,由于ecshop的后台结构特殊,页面内容大多都是由模板编译而成,而这个模板类恰好也在init.php中声明

require(ROOT_PATH . 'includes/cls_template.php'); $smarty = new cls_template;

回到order.php中我们寻找与$smarty相关的方法,不难发现,主要集中在两个方法中

... $smarty->assign('shipping', $shipping); $smarty->display('print.htm'); ...

而这里我们主要把视角集中在display方法上。

粗略的浏览下display方法的逻辑大致是

请求相应的模板文件 --> 经过一系列判断,将相应的模板文件做相应的编译 --> 输出编译后的文件地址

比较重要的代码会在make_compiled这个函数中被定义

function make_compiled($filename) { $name = $this->compile_dir . '/' . basename($filename) . '.php'; ... if ($this->force_compile || $filestat['mtime'] > $expires) { $this->_current_file = $filename; $source = $this->fetch_str(file_get_contents($filename)); if (file_put_contents($name, $source, LOCK_EX) === false) { trigger_error('can't write:' . $name); } $source = $this->_eval($source); } return $source; }

当流程走到这一步的时候,我们需要先找到我们的目标是什么?

重新审视cls_template.php的代码,我们可以发现涉及到代码执行的只有几个函数。

function get_para($val, $type = 1) // 处理insert外部函数/需要include运行的函数的调用数据 { $pa = $this->str_trim($val); foreach ($pa AS $value) { if (strrpos($value, '=')) { list($a, $b) = explode('=', str_replace(array(' ', '"', "'", '&quot;'), '', $value)); if ($b{0} == '$') { if ($type) { eval('$para['' . $a . '']=' . $this->get_val(substr($b, 1)) . ';'); } else { $para[$a] = $this->get_val(substr($b, 1)); } } else { $para[$a] = $b; } } } return $para; }

get_para只在select中调用,但是没找到能触发select的地方。

然后是pop_vars

function pop_vars() { $key = array_pop($this->_temp_key); $val = array_pop($this->_temp_val); if (!empty($key)) { eval($key); } }
说点什么吧
  • 全部评论(0
    还没有评论,快来抢沙发吧!