dedecms登陆原理初步研究
dedecms

默认dedecms是关闭会员功能的,如图所示
开启很简单,进入后台,系统-》系统基本参数-》会员设置-》是否开启会员功能,把是选上即可。
然后首页就有一个登陆功能
然后尝试登陆了一下,报这个错
首页哪来的验证码阿,我靠,dedecms你不会懒得搞验证码,才把会员功能默认关掉的吧
然后就进入一个中规中矩的登陆界面
然后测试一下,乱填一个用户名,果然
填一个存在的,密码乱写
真是不够严谨呢,我对接下来的严谨表示忧虑
登陆form表单,具体为
<form action="index_do.php" method="POST" name="form1">
可见index_do.php为核心登陆代码,二话不说打开吧!
开篇
require_once(dirname(__FILE__)."/config.php"); if(empty($dopost)) $dopost = ''; if(empty($fmdo)) $fmdo = '';
引入一个配置文件,断点得出登陆页面提交后,$dopost='login',$fmdo='login'
下面开始判断$fmdo的值了,呵,这个页面还处理挺多的功能,比如说发送邮件阿,注册阿,积分换金币阿,登陆阿,等等。其余暂时不表,先看登陆。不过,你能不能换switch,if elseif elseif 效率明显没有switch高。
看代码,
else if($fmdo=='login') { //用户登录 if($dopost=="login") { if(!isset($vdcode)) { $vdcode = ''; } $svali = GetCkVdValue(); if(preg_match("/2/",$safe_gdopen)){ if(strtolower($vdcode)!=$svali || $svali=='') { ResetVdValue(); ShowMsg('验证码错误!', 'index.php'); exit(); } }
这是判断验证码的,继续往下看
if(CheckUserID($userid,'',false)!='ok') { ResetVdValue(); ShowMsg("你输入的用户名 {$userid} 不合法!","index.php"); exit(); } if($pwd=='') { ResetVdValue(); ShowMsg("密码不能为空!","-1",0,2000); exit(); }
CheckUserID方法存在于/include/memberlogin_class.php中,具体代码为:
function CheckUserID($uid, $msgtitle='用户名', $ckhas=TRUE) { global $cfg_mb_notallow,$cfg_mb_idmin,$cfg_md_idurl,$cfg_soft_lang,$dsql; if($cfg_mb_notallow != '') { $nas = explode(',', $cfg_mb_notallow); if(in_array($uid, $nas)) { return $msgtitle.'为系统禁止的标识!'; } } if($cfg_md_idurl=='Y' && preg_match("/[^a-z0-9]/i",$uid)) { return $msgtitle.'必须由英文字母或数字组成!'; } if($cfg_soft_lang=='utf-8') { $ck_uid = utf82gb($uid); } else { $ck_uid = $uid; } for($i=0; isset($ck_uid[$i]); $i++) { if(ord($ck_uid[$i]) > 0x80) { if(isset($ck_uid[$i+1]) && ord($ck_uid[$i+1])>0x40) { $i++; } else { return $msgtitle.'可能含有乱码,建议你改用英文字母和数字组合!'; } } else { if(preg_match("/[^0-9a-z@\.-]/i",$ck_uid[$i])) { return $msgtitle.'不能含有 [@]、[.]、[-]以外的特殊符号!'; } } } if($ckhas) { $row = $dsql->GetOne("SELECT * FROM `#@__member` WHERE userid LIKE '$uid' "); if(is_array($row)) return $msgtitle."已经存在!"; } return 'ok'; }
$cfg_mb_notallow 是在后台在进行设置的,不允许注册,也不允许登陆。
$cfg_md_idurl //这个是为空的,所以中文是可以被注册的,
$ckhas //这个也是为空,注册的时候应该为true
其余的就没什么好说的,一些判断。
继续往下看
if($pwd=='') { ResetVdValue(); ShowMsg("密码不能为空!","-1",0,2000); exit(); } //检查帐号 $rs = $cfg_ml->CheckUser($userid,$pwd);
密码不为空,检查帐号?这是什么意思?我看应该是去数据库中通过用户名和密码去读数据去了,这个真的要跟踪一下了。
此类实例化于/member/config.php中
具体代码
$cfg_ml = new MemberLogin($keeptime);
此类存在于/include/memberlogin.class.php中
这是该类的实例化
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}'"); } } } #/aip}} //间隔一小时更新一次用户登录时间 if(time() - $this->M_LoginTime > 3600) { $dsql->ExecuteNoneQuery("update `#@__member` set logintime='".time()."',loginip='".GetIP()."' where mid='".$this->fields['mid']."';"); PutCookie("DedeLoginTime",time(),$this->M_KeepTime); } $this->M_LoginID = $this->fields['userid']; $this->M_MbType = $this->fields['mtype']; $this->M_Money = $this->fields['money']; $this->M_UserName = FormatUsername($this->fields['uname']); $this->M_Scores = $this->fields['scores']; $this->M_Face = $this->fields['face']; $this->M_Rank = $this->fields['rank']; $this->M_Spacesta = $this->fields['spacesta']; $sql = "Select titles From #@__scores where integral<={$this->fields['scores']} order by integral desc"; $scrow = $dsql->GetOne($sql); $this->fields['honor'] = $scrow['titles']; $this->M_Honor = $this->fields['honor']; if($this->fields['matt']==10) $this->isAdmin = TRUE; $this->M_UpTime = $this->fields['uptime']; $this->M_ExpTime = $this->fields['exptime']; $this->M_JoinTime = MyDate('Y-m-d',$this->fields['jointime']); if($this->M_Rank>10 && $this->M_UpTime>0){ $this->M_HasDay = $this->Judgemember(); } if( !$formcache ) { SetCache($this->memberCache, $this->M_ID, $this->fields, 1800); } }else{ $this->ResetUser(); } } }
上来就缓存一大片,也不管验证有没有通过,这样好吗?这是CheckUser方法
function CheckUser(&$loginuser, $loginpwd) { global $dsql; //检测用户名的合法性 $rs = CheckUserID($loginuser,'用户名',FALSE); //用户名不正确时返回验证错误,原登录名通过引用返回错误提示信息 if($rs!='ok') { $loginuser = $rs; return '0'; } //matt=10 是管理员关连的前台帐号,为了安全起见,这个帐号只能从后台登录,不能直接从前台登录 $row = $dsql->GetOne("SELECT mid,matt,pwd,logintime FROM `#@__member` WHERE userid LIKE '$loginuser' "); if(is_array($row)) { if($this->GetShortPwd($row['pwd']) != $this->GetEncodePwd($loginpwd)) { return -1; } else { //管理员帐号不允许从前台登录 if($row['matt']==10) { return -2; } else { $this->PutLoginInfo($row['mid'], $row['logintime']); return 1; } } } else { return 0; } }
这一句,
SELECT mid,matt,pwd,logintime FROM `#@__member` WHERE userid LIKE '$loginuser'
这是什么意思?不用=号连接,用的是like?逼格深不可测!
其中注意这一句
if($this->GetShortPwd($row['pwd']) != $this->GetEncodePwd($loginpwd))
两个方法 ?
function GetShortPwd($dbpwd) { global $cfg_mb_pwdtype; if(empty($cfg_mb_pwdtype)) $cfg_mb_pwdtype = '32'; $dbpwd = trim($dbpwd); if(strlen($dbpwd)==16) { return $dbpwd; } else { switch($cfg_mb_pwdtype) { case 'l16': return substr($dbpwd, 0, 16); case 'r16': return substr($dbpwd, 16, 16); case 'm16': return substr($dbpwd, 8, 16); default: return $dbpwd; } } }
function GetEncodePwd($pwd) { global $cfg_mb_pwdtype; if(empty($cfg_mb_pwdtype)) $cfg_mb_pwdtype = '32'; switch($cfg_mb_pwdtype) { case 'l16': return substr(md5($pwd), 0, 16); case 'r16': return substr(md5($pwd), 16, 16); case 'm16': return substr(md5($pwd), 8, 16); default: return md5($pwd); } }
至此,dede登陆机制已经差不多初步完成了。
主要有两种处理,一种是直接md5加密,这个安全性很差。
另外一个是通过对md5的截取,有三种截法。相对安全一些,效率稍微有些折损。