织梦(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&amp;id=".$mid."&amp;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的用户

总结

中间分析了好多,结合着漏洞利用看的代码,确实有效果。