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 内容的合法性校验;
    • 强化口令:账户最小化原则。

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

相关推荐
努力的小雨4 天前
我用 QClaw 做了个 Web3 陪学助手,专治 Java 程序员的“概念劝退”
经验分享·ai智能
两个人的幸福13 天前
Windows 桌面应用自研 PHP 队列(下):完整代码与六大工程化优化
php
BingoGo15 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
JaguarJack15 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
用户30745969820716 天前
PHP 扩展——从入门到理解
php
鹏仔先生16 天前
拷贝漫画APP下载页PHP程序,后台带免费AI写作
php
SM1771521183817 天前
NSK紧凑型FA系列丝杠技术详解
经验分享·规格说明书
云水一下17 天前
从零开始学 PHP 系列(一):PHP 的前世今生与开发环境搭建
开发语言·php
xingpanvip17 天前
星盘接口开发文档:本命盘接口指南
android·开发语言·css·php·lua
fofantasy17 天前
NSK SFT3210-2.5 滚珠丝杠技术详解
经验分享·规格说明书