DedeCMS命令执行复现&研究 | CVE-2025-6335

0x0 背景介绍

在 DеdеCMS 5.7.2及更早版本中发现了一个漏洞,并被归类为严重。此问题影响文件 /inсludе/dеdеtаɡ.class.phр 中的某个未知处理过程,该组件为模板处理器。对参数 nоtеѕ 的操作导致命令注入。

参考:CVE-2025-6335-dedeCMS后台模板注入RCE

0x1 环境搭建

1. Win10+PHPsutudy(PHP5.6x)搭建

2. 成功页面

0x2 漏洞复现

1) 手动测试tag_test_action.php,向Temp文件夹写入文件

  • 成功写入

2) 手动测试co_get_corule.php,弹出计算器

  • 成功弹出计算器

3) python脚本

  • 粗略的做了一个目标是win系统的,只是简单的验证
bash 复制代码
https://github.com/Kai-One001/cve-/blob/main/dede_cms_RCE_CVE-2025-6335.py
  • 示例脚本是向目标C:\Windows\Temp写入,也可以写入到web服务器中,脚本也是可以直接修改cmd命令比较方便一些
  • 题外,因为时间不赶趟了,所以写入目标服务器就没融入脚本

2、复现流量特征 (PACP)

1) co_get_corule.php接口

  • 弹出计算器

2) tag_test_action.php接口

  • 写入文件

0x3 漏洞原理分析

1、/dede/co_get_corule.php接口分析

PS :通过漏洞信息得知通过向 /dede/co_get_corule.php 接口发送特制参数,利用对 notes 参数的不当过滤,触发命令注入,实现远程命令执行。
1) 优先查看 /dede/co_get_corule.php文件

  • 文件位置:uploads\dede\co_get_corule.php
  • 关键代码段:
php 复制代码
require(dirname(__FILE__)."/config.php");

$cuserLogin = new userLogin();
if($cuserLogin->getUserID()==-1)
{
    header("location:login.php?gotopage=".urlencode($dedeNowurl));
    exit();
}
  • 检查是否检查用户是否登录,未登录则跳转到登录页面
php 复制代码
CheckPurview('co_AddNote');
#使用config.php内容+检查用户使用有权限(CheckPurview() 函数)

2) 跟踪定位notes参数

  • co_get_corule.php发现多处使用,进行过滤发现大抵流程是 当 $job 不为空时,进入notes
  • 再往下面逻辑是base64解码去除下反斜杠,接着往下看
php 复制代码
$notes = trim($notes);

3) 模板解析和标签提取

  • 关键代码:
php 复制代码
$dtp = new DedeTagParse();
$dtp->LoadString($notes);
  • 调用路径追踪:
bash 复制代码
LoadString($str)
 └──> LoadSource($str)
       └──> LoadTemplate($filename)
             └──> ParseTemplet()
                   └──> 提取 {dede:xxx} 标签 → 构建 CTags 数组
  • $notes 作为 DedeCMS 模板字符串进行解析
  • 这里会用到dedetag.class.php因为LoadString()
  • 会调用提取所有 {dede:xxx} 标签 后续LoadTemplate()读取该文件并触发 ParseTemplet()
    4) 查看LoadString() 方法的实现
  • 文件位置:uploads/include/dedetag.class.php
php 复制代码
function LoadString($str)
{
    $this->LoadSource($str);
}
  • 调用了 LoadSource($str),查看具体方法
php 复制代码
    function LoadSource($str)
    {
        /*
        $this->SetDefault();
        $this->SourceString = $str;
        $this->IsCache = FALSE;
        $this->ParseTemplet();
        */
        //优化模板字符串存取读取方式
        $this->taghashfile = $filename = DEDEDATA.'/tplcache/'.md5($str).'.inc';
        if( !is_file($filename) )
        {
            file_put_contents($filename, $str);
        }
        $this->LoadTemplate($filename);
    }
  • 调用了 LoadTemplate($filename),在进去套娃
php 复制代码
function LoadTemplate($filename)
{
    $this->SetDefault();
    if(!file_exists($filename))
    {
        $this->SourceString = " $filename Not Found! ";
        $this->ParseTemplet(); //  直接调用 ParseTemplet
    }
    else
    {
        $fp = @fopen($filename, "r");
        while($line = fgets($fp,1024))
        {
            $this->SourceString .= $line;
        }
        fclose($fp);
        if($this->LoadCache($filename))
        {
            return '';
        }
        else
        {
            $this->ParseTemplet(); //  正常流程也调用 ParseTemplet
        }
    }
}
  • 到这里就看到所以无论文件是否存在,只要没有缓存命中,最终都会调用 ParseTemplet()
    5)那就查看ParseTemplet方法
  • 这个方法的作用是:

遍历 $this->SourceString

找出所有{dede:xxx}开头的标签

提取标签名、属性、内嵌文本(InnerText)

构建DedeTag对象并存入 $this->CTags 数组

php 复制代码
$FullTagStartWord =  $TagStartWord.$this->NameSpace.":";
...
$sPos = strpos($this->SourceString, $FullTagStartWord, $ss);
  • 然后提取 tTagName,解析属性,设置 InnerText,最后加入CTags数组。
    6)跟踪CTags函数
  • 是在 dedetag.class.php 中,SaveCache() 方法将标签信息写入缓存文件(.inc)
php 复制代码
    function SaveCache()
    {
        $fp = fopen($this->CacheFile.'.txt',"w");
        fwrite($fp,$this->TempMkTime."\n");
        fclose($fp);
        $fp = fopen($this->CacheFile,"w");
        flock($fp,3);
        fwrite($fp,'<'.'?php'."\r\n");
        $errmsg = '';
        if(is_array($this->CTags))
        {
            foreach($this->CTags as $tid=>$ctag)
            {
                $arrayValue = 'Array("'.$ctag->TagName.'",';
                if (!$this->CheckDisabledFunctions($ctag->InnerText, $errmsg)) {
                    fclose($fp);
                    @unlink($this->taghashfile);
                    @unlink($this->CacheFile);
                    @unlink($this->CacheFile.'.txt');
                    die($errmsg);
                }
                $arrayValue .= '"'.str_replace('$','\$',str_replace("\r","\\r",str_replace("\n","\\n",str_replace('"','\"',str_replace("\\","\\\\",$ctag->InnerText))))).'"';
                $arrayValue .= ",{$ctag->StartPos},{$ctag->EndPos});";
                fwrite($fp,"\$z[$tid]={$arrayValue}\n");
                if(is_array($ctag->CAttribute->Items))
                {
                    fwrite($fp,"\$z[$tid][4]=array();\n");
                    foreach($ctag->CAttribute->Items as $k=>$v)
                    {
                        $v = str_replace("\\","\\\\",$v);
                        $v = str_replace('"',"\\".'"',$v);
                        $v = str_replace('$','\$',$v);
                        $k = trim(str_replace("'","",$k));
                        if($k=="")
                        {
                            continue;
                        }
                        if($k!='tagname')
                        {
                            fwrite($fp,"\$z[$tid][4]['$k']=\"$v\";\n");
                        }
                    }
                }
            }
        }
        fwrite($fp,"\n".'?'.'>');
        fclose($fp);
    }
  • $ctag->TagName 是从用户输入中提取的标签名(如 {dede:xxx} 中的 xxx)
  • 直接拼接到PHP代码中,未进行转义或过滤
  • 直接拼接到 fwrite 输出的 PHP 代码中
  • 构造恶意"标签名"即可在缓存inc中打断字符串并插入任意 PHP,例如:
bash 复制代码
{dede:ewoji");system('calc');///} → 下次读缓存时被包含执行
  • 缓存文件的生成位置也就是 /data/tplcache/md5($notes).inc同一个恶意Payload每次都会生成相同的缓存文件名
  • 攻击载荷示例:
bash 复制代码
{dede:ewoji");system('calc');//}
  • ParseTemplet() 解析出标签名:ewoji");system('calc');//
  • SaveCache() 中生成缓存文件 .inc 内容如下:
php 复制代码
<?php
$z[0]=Array("ewoji");system('calc');//", "...", ...);
?>
  • 当该缓存文件被后续 include 或 require 加载时,system('calc') 将被执行

2、/dede/tag_test_action.php接口分析

1) 同上接口,可控参数导致直接拼接注入

php 复制代码
<?php
require_once(dirname(__FILE__)."/config.php");
CheckPurview('temp_Test');           
require_once(DEDEINC."/arc.partview.class.php");
csrf_check();       
  • co_get_corule.php接口相同,对权限进行检查,不同的是会有csrf-token检查

  • 在测试时,我根据参考进行直接POST,果然会提示DedeCMS:CSRF Token Check Failed!

  • 于是找到web页面进行抓包,看到有一个token,可以正常使用(这个token就是在当前接口的HTML中)

2) 转义绕过

php 复制代码
$partcode = stripslashes($partcode); // 去除斜杠 
  • 作用:去除GPC自动添加的反斜杠(如 ' → \'),但是即使输入中包含引号、括号等特殊字符,也能被还原

3) SetTemplet() 将用户输入的 $partcode 作为"模板字符串"传入

php 复制代码
// 设置模板内容为字符串形式
$pv->SetTemplet($partcode, "string");

// 显示源码(可选)
if( $showsource == "" || $showsource == "yes" ) {
    echo "模板代码:";
    echo "<span style='color:red;'><pre>".dede_htmlspecialchars($partcode)."</pre></span>";
    echo "结果:<hr size='1' width='100%'>";
}

// 执行显示
$pv->Display();
  • 当标签闭合不当或拼接到PHP 代码中时,可能触发代码注入
bash 复制代码
调用追踪如下
$pv->Display()
 └── $this->MakeHtml() 
      └── $this->dtp->ParseTemplate($this->SourceString)
           └── 调用 DedeTagParse 类解析标签
                └── 对 {dede:...} 进行 eval 或 create_function 执行
  • DedeCMS 在处理 {dede:xxx} 标签时,会将其转换为PHP代码片段。例如:
bash 复制代码
传入:{dede:test /}
被转换为:
<?php echo GetTagData('test'); ?>
如果标签内容异常,如:
{dede:test"}); system('calc'); //}
在拼接进 PHP 缓冲区时,可能导致语法闭合 + 代码注入

0x4 修复建议

修复方案

  1. 升级到最新版本? :目前厂商说已发布升级补丁以修复漏洞,但是我下载也是这个版本的V5.7.118

  2. 临时缓解措施

    • 禁用危险字符拼接 :SaveCache() 方法,对 $ctag->TagName 进行转义;
    • 白名单机制:只允许预定义的合法标签名(如 field, list, arc 等),其余一律拒绝;
    • 输入过滤:增加对notes 内容的合法性校验;
    • 强化口令:账户最小化原则。

免责声明:本文仅用于安全研究目的,未经授权不得用于非法渗透测试活动。

相关推荐
2403_8728777210 小时前
2025,跨领域发展的低门槛技能切入路径
经验分享
聪明的笨猪猪11 小时前
Java SE “面向对象”面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
聪明的笨猪猪11 小时前
Java 集合 “List + Set”面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
毕设源码-郭学长11 小时前
【开题答辩全过程】以 PHP茶叶同城配送网站的设计与实现为例,包含答辩的问题和答案
开发语言·php
聪明的笨猪猪14 小时前
Java SE “泛型 + 注解 + 反射”面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
智者知已应修善业16 小时前
【51单片机计时器1中断的60秒数码管倒计时】2023-1-23
c语言·经验分享·笔记·嵌入式硬件·算法·51单片机
聪明的笨猪猪17 小时前
Java 集合 “Map(1)”面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
bmcyzs1 天前
【展厅多媒体】解析VR虚拟驾驶实现多场景自由切换
经验分享·科技·信息可视化·软件构建·vr·设计规范
Metaphor6921 天前
Java 在Word 文档中添加批注:高效文档协作的利器
经验分享