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

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

缺陷编号:wooyun-2014-064037

漏洞标题:phpdisk V7 (20140604) 绕过补丁继续上传任意文件。

相关厂商:phpdisk.com

漏洞作者: ′雨。

提交时间:2014-06-11 09:01

修复时间:2014-09-09 09:02

公开时间:2014-09-09 09:02

漏洞类型:文件上传导致任意代码执行

危害等级:高

自评Rank:20

漏洞状态:厂商已经确认

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

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2014-06-11: 细节已通知厂商并且等待厂商处理中
2014-06-11: 厂商已经确认,细节仅向厂商公开
2014-06-14: 细节向第三方安全合作伙伴开放
2014-08-05: 细节向核心白帽子及相关领域专家公开
2014-08-15: 细节向普通白帽子公开
2014-08-25: 细节向实习白帽子公开
2014-09-09: 细节向公众公开

简要描述:

验证encrypt_key.
这个会上首页吗。。 好紧张。。

详细说明:

首先说一下 官方的demo站竟然还没打补丁。
我进去的时候已经看见里面有几个马儿了。。 打下补丁 清下马儿把。
来看看0604出的补丁修改了哪里。
在plugins/phpdisk_client/client_sub.php

switch ($action){
case 'upload_file':

//write_file(PHPDISK_ROOT.'system/2.txt',var_export($_POST,true));
//write_file(PHPDISK_ROOT.'system/3.txt',var_export($_FILES,true));
$sign_md5 = md5($uid.$settings[encrypt_key]);
if(!$sign and $sign_md5<>$sign){
echo 'Sign Error!';
exit;
}


在这里上传的时候验证了

$sign_md5 = md5($uid.$settings[encrypt_key]);
if(!$sign and $sign_md5<>$sign){
echo 'Sign Error!';
exit;



如果不相等则退出. $uid倒还容易搞 来看看$settings[encrypt_key]

'encrypt_key' => 'Bw5xe2XIlwwj',

我擦 这么复杂?
再来看看是如何生成这个的。

function make_key(){
var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var tmp = "";
var code = "";
for(var i=0;i<12;i++){
code += chars.charAt(Math.ceil(Math.random()*100000000)%chars.length);
}
document.getElementById('encrypt_key').value = code;
}


这么多字符中随机取12个。 我擦 放弃逆这个了。
在plugins/phpdisk_client/client_sub.php中

$agent = $_SERVER['HTTP_USER_AGENT'];
if($agent!='phpdisk-client'){
exit('<a href="http://faq.phpdisk.com/search?w=p403&err=code" target="_blank">[PHPDisk Access Deny] Invalid Entry!</a>');
}
$u_info = trim(gpc('u_info','P',''));
parse_str(pd_encode(base64_decode($u_info),'DECODE'));


parse_str(pd_encode(base64_decode($u_info),'DECODE'));
这里调用了自定义的pd_encode 来解密
如果可以逆到key 就可以自己通过key来生成一个加密的 然后解密之后就可以变量覆盖。

function pd_encode($string, $operation = 'ENCODE',$key = ''){
global $settings;
$ckey_length = 4;
$key = md5($key ? $key : ($settings['encrypt_key'] ? $settings['encrypt_key'] : 'PHPDisk=Rc9o'));
$keya = md5(substr($key, 0, 16));
$keyb = md5(substr($key, 16, 16));
$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
$cryptkey = $keya.md5($keya.$keyc);
$key_length = strlen($cryptkey);
$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d',0).substr(md5($string.$keyb), 0, 16).$string;
$string_length = strlen($string);
$result = '';
$arr = 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 + $arr[$i] + $rndkey[$i]) % 256;
$tmp = $arr[$i];
$arr[$i] = $arr[$j];
$arr[$j] = $tmp;
}
for($a = $j = $i = 0; $i < $string_length; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $arr[$a]) % 256;
$tmp = $arr[$a];
$arr[$a] = $arr[$j];
$arr[$j] = $tmp;
$result .= chr(ord($string[$i]) ^ ($arr[($arr[$a] + $arr[$j]) % 256]));
}
if($operation == 'DECODE') {
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 {
return $keyc.str_replace('=', '', base64_encode($result));
}
}


调用了那12位的key。。 对算法不懂 放弃了。。
那就找找有没有哪里调用这函数来进行加密的。
如果要加密的可控的话也行

那就来找找在哪里调用了这函数来进行加密了的。
在plugins/phpdisk_client/client_main.php中

if($action && $action<>'download'){
$agent = $_SERVER['HTTP_USER_AGENT'];
if($agent!='phpdisk-client'){
exit('<a href="http://faq.phpdisk.com/search?w=p403&err=code" target="_blank">[PHPDisk Access Deny] Invalid Entry!</a>');
}
}
// checked username and pwd...
$username = trim(gpc('username','GP',''));
$password = trim(gpc('password','GP',''));
$username = is_utf8() ? convert_str('gbk','utf-8',$username) : $username;
$password = is_utf8() ? convert_str('gbk','utf-8',$password) : $password;
$rs = $db->fetch_one_array("select * from {$tpf}users where username='$username' and password='$password'");
if(!$rs){
$str = '网盘登录出错:用户名或密码不正确,请重新输入';
if(is_utf8()){
echo convert_str('utf-8','gbk',$str);
}else{
echo $str;
}
exit;
}else{
if($rs[is_locked]){
$str = '网盘登录出错:用户名被锁定';
if(is_utf8()){
echo convert_str('utf-8','gbk',$str);
}else{
echo $str;
}
exit;


验证了user agent 可以修改一下就行了
然后去注册一个号就行了。 再往下面看。

case 'loadset':
if($settings['open_multi_server']){
$server_host = @$db->result_first("select server_host from {$tpf}servers where server_id>1 order by is_default desc limit 1");
}
$server_host = $server_host ? trim($server_host) : $settings[phpdisk_url];
$sign = md5($uid.$settings[encrypt_key]);
echo 'true'.LF;
echo $server_host.LF;
echo '0'.LF;
echo base64_encode(pd_encode('username='.$username.'&password='.$password.'&sign='.$sign)).LF;
echo $settings[client_api_key];
exit;
break;


这里调用了pd_encode 不是DECODE 是ENCODE。 看看里面的。

echo base64_encode(pd_encode('username='.$username.'&password='.$password.'&sign='.$sign)).LF;
$sign = md5($uid.$settings[encrypt_key]);


。。可以看到 自己把做验证的带入到了pd_encode里面 然后输出了。。
这特么的太爽了。

漏洞证明:

首先注册一个号

p5.jpg


由于密码他这里没有md5。 所以自己把自己的密码进行md5一次后再放进去。
然后得到加密字符串。
MjdjNWpzd0lOYTFtQTd6R1l1alkxRlhlS2ZiYnc4azV1VFIyNHFXLzluZ1p1K2JFOVdqZlRTbVJXMXZLL0FYb21ScGlVMU5wcU1hSjZXOHYzZXk4MnpOWU1pdk1oV2Zzb0RTQk9tNHdCYWpjeHNUWG9sZUtMK0s5VzlrMUJhNzkrOXgrSVV2dTZrVitscURFZk16djJtM0lsWjV6OUZvSE9JU0lUZw==
然后在client_sub.php中

$u_info = trim(gpc('u_info','P',''));
parse_str(pd_encode(base64_decode($u_info),'DECODE'));


解密后 就注册了$sign变量。
就通过了这个验证 就能继续上传任意文件了。

p10.jpg


sign错误 把刚才的加密字符串复制进去。

p11.jpg


上传成功。

p12.jpg


嗯。
_________________________________________________________________________
测试一下demo。

p13.jpg


成功得到加密字符串。
在测试过程中发现不用这加密的也能上传成功。
看看原因。

p14.jpg


原来官方竟然都忘记给自己的demo站打补丁了。
出于人道我给官方把补丁弄上去把。

switch ($action){
case 'upload_file':
//write_file(PHPDISK_ROOT.'system/2.txt',var_export($_POST,true));
//write_file(PHPDISK_ROOT.'system/3.txt',var_export($_FILES,true));
$sign_md5 = md5($uid.$settings[encrypt_key]);
if(!$sign and $sign_md5<>$sign){
echo 'Sign Error!';
exit;
}


不过这补丁还得继续修改 因为能像上面那样绕过。

修复方案:


加强验证。

版权声明:转载请注明来源 ′雨。@乌云


漏洞回应

厂商回应:

危害等级:高

漏洞Rank:15

确认时间:2014-06-11 13:04

厂商回复:

感谢反馈

最新状态:

暂无