织梦(DedeCms)也是一个国产内容管理系统,曾经爆出过众多漏洞,甚至还有人开发了dedecms漏洞一键扫描器
安装过程也很简单,本次采用的是DedeCMS v5.7sp2
全局分析
首先这个是有前台和后台的
前台的功能点很少,后台的管理员界面的功能点多
一般分析的话首先从入口文件开始分析。
前台的入口文件
首先是前台的index.php
这个就是安装页面,没有什么好看的,我们接下往下看
接着加载了/include/common.inc.php文件
我们跟进去看一下
这里都是定义了一些全局变量
会对GPC变量进行处理
CheckRequest($_REQUEST);
CheckRequest($_COOKIE);
foreach(Array('_GET','_POST','_COOKIE') as $_request)
{
foreach($$_request as $_k => $_v)
{
if($_k == 'nvarname') ${$_k} = $_v;
else ${$_k} = _RunMagicQuotes($_v);
}
}
}
这里有个_RunMagicQuotes()函数就是检查函数
加载了一些数据库操作的文件,还定义了一些目录,这里还加载了文件上传的文件,这个我们重点关注一下
我们给进一下这个文件上传安全的文件
在/include/uploadsafe.inc.php中
<?php
if(!defined('DEDEINC')) exit('Request Error!');
if(isset($_FILES['GLOBALS'])) exit('Request not allow!');
//为了防止用户通过注入的可能性改动了数据库
//这里强制限定的某些文件类型禁止上传
$cfg_not_allowall = "php|pl|cgi|asp|aspx|jsp|php3|shtm|shtml";
$keyarr = array('name', 'type', 'tmp_name', 'size');
if ($GLOBALS['cfg_html_editor']=='ckeditor' && isset($_FILES['upload']))
{
$_FILES['imgfile'] = $_FILES['upload'];
$CKUpload = TRUE;
unset($_FILES['upload']);
}
foreach($_FILES as $_key=>$_value)
{
foreach($keyarr as $k)
{
if(!isset($_FILES[$_key][$k]))
{
exit('Request Error!');
}
}
if( preg_match('#^(cfg_|GLOBALS)#', $_key) )
{
exit('Request var not allow for uploadsafe!');
}
$$_key = $_FILES[$_key]['tmp_name'];
${$_key.'_name'} = $_FILES[$_key]['name'];
${$_key.'_type'} = $_FILES[$_key]['type'] = preg_replace('#[^0-9a-z\./]#i', '', $_FILES[$_key]['type']);
${$_key.'_size'} = $_FILES[$_key]['size'] = preg_replace('#[^0-9]#','',$_FILES[$_key]['size']);
if(!empty(${$_key.'_name'}) && (preg_match("#\.(".$cfg_not_allowall.")$#i",${$_key.'_name'}) || !preg_match("#\.#", ${$_key.'_name'})) )
{
if(!defined('DEDEADMIN'))
{
exit('Not Admin Upload filetype not allow !');
}
}
if(empty(${$_key.'_size'}))
{
${$_key.'_size'} = @filesize($$_key);
}
$imtypes = array
(
"image/pjpeg", "image/jpeg", "image/gif", "image/png",
"image/xpng", "image/wbmp", "image/bmp"
);
if(in_array(strtolower(trim(${$_key.'_type'})), $imtypes))
{
$image_dd = @getimagesize($$_key);
if (!is_array($image_dd))
{
exit('Upload filetype not allow !');
}
}
}
?>
这里的限制主要是有个上传的黑名单
还有一个对上传类型的判断
除此之外,它只能admin用户进行上传,而且对文件的名字进行判断,名字不能为空,不能没有后缀
下面对文件类型判断之后,这里有个函数会对文件内容进行判断,这个函数可以使用文件头进行绕过
后面就是很常见的文件了
回到index.php
这里还加载了视图文件
后面就没有什么了
前台的功能点比较少,这个index.php也是一个静态的页面
跟踪后台的入口文件
后台的入口文件在dede
dede/index.php
入口文件很简单,加载了配置文件/config.php
我们跟进去看一下
这里还是加载了这个common.inc.php文件
后面是对用户的登入状态进行验证
接下来就是有人csrf的检测和Xss的检测
后台比较简单,可以通过谷歌的调试工具看出来,这个是通过ifame来加载这个index_menu.php和index_body.php文件的
漏洞审计
任意文件上传
普通的文件上传
在后台
这里的添加文档
这里有文件上传功能
通过抓包我们得知,这里的上传文件是dede/archives_do.php
但是这里的前端会验证文件的后缀
我们现在看一下这个dede/archives_do.php
这里也是加载了confing.php文件
最后触发/uploadsafe.inc.php的过滤
最终实现文件上传的AdminUpload()
来自upload.helper.php
我们跟进看一下
这里会对类型判断,而且这里的ftypr我们也要设置成相应的值
上面的全局分析会对文件的内容进行判断,这里我们也能进行绕过
GIF89a
<?php
phpinfo();
?>
这是我们的文件内容,要先改成.jpg后缀的,抓包之后改成php后缀的
这里会有路径
成功
如果是黑盒测试,那么来个GIF89a,可能就成功了
尴尬的文件上传
这里可以直接文件上传,没有任何过滤
有趣的文件上传
在会员中心,我们可以上传文件,这里需要登入管理员界面开启会员功能
我们抓包知道,这里的文件是include/dialog/select_images_post.php
我们看一下这个文件
这里加载了配置文件,会对登入进行验证
这个还加载了基础的检查文件
上面的全局分析得知,这里会有基础的文件后缀检测
这里是对特殊字符的过滤,我们就可以在上传的文件后加上 *
就像这样 .p*hp
这里只要文件名字里面有这个字段就可以了
xss
通过我们上面的分析知道,DedeCMS是一个多入口文件,大多数的外部数据都会经过全局过滤函数进行判断数据是否安全,如果说哪些外部数据没有经过全局过滤的话,就可能会存在xss漏洞,这里我们可以黑盒测试一下
在plus/qrcode.php中
入口文件没有包含这个全局配置文件,这里没有对数据进行过滤
这里的id值只能为数字,这里的type我们可控
这里加载了这个模板
payload
/plus/qrcode.php?id=1&type="><ScRiPt>alert(1)</ScRiPt>
会员中心任意用户密码修改
不知道为什么我这个成功不了,根据漏洞复现,这个是需要没有设置安全提示的用户才能修改密码,其原因就是在进行比较的时候,进行了
弱比较,这个是可以绕过的,但是我在复现的时候,一直提醒我有非法操作,后来我自己又注册了一个账号,这个账号是我自己设置了问题
经过问题来修改密码,但是很遗憾的是,这个正常情况下,也提示我有非法操作,不知道版本是否有问题,还是怎么了,通过动调,验证是
可以绕过的,但是最后不知道是怎么了,提示我非法操作
现来分析一下这个洞
他是在这个密码重设这里
首先加载了自己的配置文件,这配置文件就是检查一下是否用户登入了,会员中心是否开启,接下就是
xss过滤什么的
这个就是判断一些具体的操作了,这是第一步填写基本信息,填要修改的用户名,邮箱还有就是找回的方式
接着就是第二步,填写一下自己的验证的问题
判断成功之后就会进入下面的这个sn函数
这里会发送验证码,这里有个newmail函数,我们跟进一下
这里的话我们在前面已经定义好了,之后会跳转到新的页面
return ShowMsg('稍后跳转到修改页', $cfg_basehost.$cfg_memberurl."/resetpassword.php?dopost=getpasswd&id=".$mid."&key=".$randval);
这里有个key相比就是验证码了
之后就是到了更新密码的操作了
下面就是判断这个key是不是真的key,这个key生成是随机的八位数字
这里是随机的八位数字,更新到这个tmp表中的pwd字段用于后续的验证
之后验证成功之后就可以修改密码了
这里就是关键在于怎么去绕过这个用户问题提示,这里是用了弱比较
默认情况下如果没有设置安全问题的话,就是为0,这里我们就用0就可以绕过
但是这里的safequestion判断是否为空,如果是空的话,这里就是""
这里我们可以用弱比较绕过
"0" == "00" //true
"0" == "0.0" //true
"0" == "0e1" //true
随便选一个就可以了
这里为了方便我把验证码给注销了
post
dopost=safequestion&gourl=&userid=test&mail=1%40qq.com&type=2&safeanswer=0&id=2&safequestion=00
发包之后会给验证码
我访问这个url之后会说有非法操作,然后我就post发包了
成功
这次还是遇到的问题挺多的,首先就是非法操作的问题,这个整的云里雾里的,开始的时候没看清楚,后来动调了一下
这次还是自己审计下来,收获很多
会员中心任意用户登陆
这个会员中心任意用户登入的漏洞产生原因我差不多搞清楚了,但是还有一些东西云里雾里的,我们先试着分析一下
首先分析一下入口文件,这里的入口文件就是判断一下这个用户是否登入了
cookie分析
这里还是先加载了配置文件我们跟进一下
这里的是加载了include/common.inc.php,这是个全局配置文件,下面还加载了登入类
这里是判断了是否开启了会员中心的功能,我们可以在后台管理员进行修改
下面就是实例化了一个新对象,这里的新对象调用了IsLogin来判断是否登入了
这里面是通过这个M_ID是否大于0来判断的
再往下面就是判断这个资料完善了没,统计文章数量,都是需要用IsLogin()来判段
在实例化这个对象的时候会触发这个对象的__construct()方法
主要是利用这个进行越权的
通过抓包我们得知
这个登入的时候是在这个index_do.php这个文件中,我们来具体看一下
我们现在进入的是这个分支
这里面是检查账号的操作,这里就是主要检查一下用户名是否合法,账号是否为admin,admin不能从前台登入
如果都不是的话,就是登入成功,这里的话调用这个PutLoginInfo()函数
我们跟进一下
这里的$uid就是传进来的mid,logintime就是从数据库里面更新的登入时间,这个是判断增加积分的功能
没有什么用
我们跟进一下这个接着往后看
获得登入的ip,登入的ID号,就是这个UID,这个UID就是我们的mid,登入时间就是这个time()函数获取的
接下来就是设置cookie了,原来的时候一直没有搞明白这个cookie是怎么来的,这次明白了
设置了两个cookie,我们重点关注一下第一个,因为这里的参数我们是可控的
这里是有两个cookie的,一个是key,一个是key__ckMd5
登入成功之后会返回index.php,这里我们继续跟进
这里会判断uid是否为空,这里我们就直接url请求一个uid就可以
接下来到了这里
先请求一个这个文件,这个就是检查一下用户的个人空间是否完整
接下来就是获得Cookie操作,我们跟进去看一下
if ( ! function_exists('GetCookie'))
{
function GetCookie($key)
{
global $cfg_cookie_encode;
if( !isset($_COOKIE[$key]) || !isset($_COOKIE[$key.'__ckMd5']) )
{
return '';
}
else
{
if($_COOKIE[$key.'__ckMd5']!=substr(md5($cfg_cookie_encode.$_COOKIE[$key]),0,16))
{
return '';
}
else
{
return $_COOKIE[$key];
}
}
}
}
这里就是那个获得cookie的功能
解题思路
说了这么多,可能大家都晕了,我也晕了,这个东西看了很长时间,总没有搞清楚这个到底是干嘛用的,今天突然醒悟这个是个越权漏洞,这里我们要伪造admin用户的cookie进行登入,不管是admin,其他用户我们同样可以伪造cookie,但是伪造cookie我们需要密钥吧,这个密钥我们是不知道的
也就是这个东西我们是不知道的
我们通过代码审计,或者是其他的方法发现,这个设置cookie的规则就是这个算是密钥的东西加上这个这个value进行md5加密之后提取16个字节,这个就是我们的cookie,经过审计发现这个value就是我们的用户的mid号,Dedecms的admin的用户的mid就是1
知道了这些,我们看member/index.php,这里面允许我们访问其他的用户,但是会留下自己的cookie,这个cookie的设置规则和用户登入设置cookie的规则不同,这个还是密钥加上我们的value,但是我们的value变成了其他用户的用户名,不是其他用户mid号了,这样做的目的就是为了防止越权别人,假如说我们去访问别人的时候,这个cookie是别人的mid号,那我们就可以随便越权了
那就有人说了,那我把我自己的用户名设置成数字,不就可以了,假如说我想越权admin,我注册一个名字为1的用户名,这样我访问这个1的时候不就知道了这个cookie,当然是不行的,这个你能想到,别人也能想到,这里就是限制了用户名不能太短,如果太短就会直接退出
那我们只能老老实实的审计了,看看有什么
下面有个$uid的赋值
看一下触发的条件
上面有个获得last_vid的操作,下面的判断就是这个last_vid为空就可以进去这个判断,我们看一下这个怎么wei
第一个if判断我们基本上绕不过,看看第二个if判断,判断我们这里是否有过登入,就是
$_COOKIE[$last_vid.'__ckMd5']!=substr(md5($cfg_cookie_encode.$last_vid),0,16)
这里的话我们没有这个cookie就可以就可以返回空
如果没有的话就是直接让last_vid=uid,这里的话我们就可以控制这个last_vid了
接着我们得到last_vid的值,可以知道这个last_vidcookie的值
得知这些之后我们该怎么利用呢
首先我们可以知道这个last_vid的值和last_vidcookie的值,这里我们先注册一个用户为1admin,这个用户将作为我们访问的用户
我们访问这个用户的个人中心
会得到两样东西,就是这个
last_vid = 1admin
last_vid_ckMd5 = xxxxxxxxx
上面还有这个DedeUserID,我们将这两个替换到上面的两个
last_vid__ckMd5->DedeUserID__ckMd5
last_vid->DedeUserID
替换完成之后,我们访问这个页面,那么这个网站会做什么呢
当然是检验cookie了
因为我们这个key和keycookie本来就是一对的,所以检验cookie没有问题
但是这个网站在进行查询的时候是跟据这个DedeUserID查询的,本来我们的DedeUserID是一个数字,现在替换成了1admin
那怎么查询,当然这就是漏洞关键所在了
这里会强制类型转化,这里把1admin转化为了1,这就导致了查询的是mid为1的用户也就是admin了
这里我们可以不用1admin,我们可以用以1开头的任何字符,也可以开头为2查询uid为2的用户
总结
中间分析了好多,结合着漏洞利用看的代码,确实有效果。