漏洞触发点在include/memberlogin.class.php中的MemberLogin类中的登录校验函数
//php5构造函数 function __construct($kptime = -1, $cache=FALSE) { global $dsql; if($kptime==-1){ $this->M_KeepTime = 3600 * 24 * 7; }else{ $this->M_KeepTime = $kptime; } $formcache = FALSE; $this->M_ID = $this->GetNum(GetCookie("DedeUserID")); $this->M_LoginTime = GetCookie("DedeLoginTime"); $this->fields = array(); $this->isAdmin = FALSE; if(empty($this->M_ID)) { $this->ResetUser(); }else{ $this->M_ID = intval($this->M_ID); if ($cache) { $this->fields = GetCache($this->memberCache, $this->M_ID); if( empty($this->fields) ) { $this->fields = $dsql->GetOne("Select * From `#@__member` where mid='{$this->M_ID}' "); } else { $formcache = TRUE; } } else { $this->fields = $dsql->GetOne("Select * From `#@__member` where mid='{$this->M_ID}' "); } if(is_array($this->fields)){ #api{{ if(defined('UC_API') && @include_once DEDEROOT.'/uc_client/client.php') { if($data = uc_get_user($this->fields['userid'])) { if(uc_check_avatar($data[0]) && !strstr($this->fields['face'],UC_API)) { $this->fields['face'] = UC_API.'/avatar.php?uid='.$data[0].'&size=middle'; $dsql->ExecuteNoneQuery("UPDATE `#@__member` SET `face`='".$this->fields['face']."' WHERE `mid`='{$this->M_ID}'"); } } }
可以看到M_ID参数是由GetNum(GetCookie("DedeUserId"))赋值的。
之后通过intval()函数对M_ID进行整数转换便没有再进行任何处理和过滤,然后通过SQL语句Select * From '#@__member' where mid='{$this->M_ID}'查询来判断用户的身份。
因此我们跟进GetCookie()函数和GETNum()函数,查看是否可以通过修改M_ID参数来尝试是否可以进行越权。
/** * 获取Cookie记录 * * @param $key 键名 * @return string */ if ( ! function_exists('GetCookie')) { function GetCookie($key) { global $cfg_cookie_encode; if( !isset($_COOKIE[$key]) || !isset($_COOKIE[$key.'__ckMd5']) ) { return ''; } else { if($_COOKIE[$key.'__ckMd5']!=substr(md5($cfg_cookie_encode.$_COOKIE[$key]),0,16)) { return ''; } else { return $_COOKIE[$key]; } } } }
/** * 获取整数值 * * @access public * @param string $fnum 处理的数值 * @return string */ function GetNum($fnum){ $fnum = preg_replace("/[^0-9.]/", '', $fnum); return $fnum; }
根据传入的字符串,找到cookie里相应的值,但这里对$_COOKIE[$key.'__ckMd5']进行了校验,使用的$ cfg_cookie_encode是在程序安装时设置的。
所以如果我们不知道这个值,我们是无法破解$key.'__ckMd5'。所以一般情况下,我们是无法破解$key.'__ckMd5'这类值的。
GetNum()函数则是对GetCookie()函数返回的$_COOKIE[$key]值进行处理,获取整数值。
正常情况下,程序中的DedeUserID就是当前用户的用户ID。但是如果将其中的DedeUserID和DedeUserID__ckMd5都替换为其他的账户,是否就意味着我们可以以其他的账户登录呢?
那么现在的问题就在于,如何进行伪造呢?我们知道管理员admin的mid是1,但是我们如何得到对应的DedeUserID__ckMd5呢?所以目前的问题转变为我们需要得到1所对应的DedeUserID__ckMd5的。
漏洞利用点:
在文件/menber/index.php中,找到这么一段代码
/*----------------------------- //会员空间主页 function space_index(){ } ------------------------------*/ else { require_once(DEDEMEMBER.'/inc/config_space.php'); if($action == '') { include_once(DEDEINC."/channelunit.func.php"); $dpl = new DedeTemplate(); $tplfile = DEDEMEMBER."/space/{$_vars['spacestyle']}/index.htm"; //更新最近访客记录及站点统计记录 $vtime = time(); $last_vtime = GetCookie('last_vtime'); $last_vid = GetCookie('last_vid'); if(empty($last_vtime)) { $last_vtime = 0; } if($vtime - $last_vtime > 3600 || !preg_match('#,'.$uid.',#i', ','.$last_vid.',') ) { if($last_vid!='') { $last_vids = explode(',',$last_vid); $i = 0; $last_vid = $uid; foreach($last_vids as $lsid) { if($i>10) { break; } else if($lsid != $uid) { $i++; $last_vid .= ','.$last_vid; } } } else { $last_vid = $uid; } PutCookie('last_vtime', $vtime, 3600*24, '/'); PutCookie('last_vid', $last_vid, 3600*24, '/');
可以看出$last_vid为空则把$uid赋值给$last_vid,而这个$uid就是可控的用户名,再通过PutCookie()函数写到cookie中。
这里加入构造出类似0000001或1abcde这样的用户名,用我last_vid_ckMd5的值 == ($cfg_cookie_encode.$_COOKIE['last_vie'])的MD5的前16位,满足条件。
接着在登录类的构造函数中,mid经GetNum()和intval()函数的过滤,就形成了1,接着进入数据库查询再展示到页面。
在用户空间主页:
http://localhost/DedeCMS/member/index.php?uid=001
这里代码根据用户提交的参数uid调用了Getcookie()函数生成了cookie值即相应的$_COOKIE[$key]和$_COOKIE[$key.'__ckMd5']值。
由于GetNum()函数的处理,我们在复现的时候注册攻击账号时应使用001、0001、00001之类的用户名来绕过该函数,从而达到以管理员的身份登录前台。
---V5.7_UTF8_SP1、SP2---