1. 程式人生 > >DeDecms(織夢CMS)最新版任意使用者密碼重置漏洞分析

DeDecms(織夢CMS)最新版任意使用者密碼重置漏洞分析

描述

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");
}