dedecms在对前台用户认证时采用的是通过cookie的验证方式,通过Cookie中的DedeUserID
和DedeUserID__ckMd5
对当前的用户的身份以及登录状态进行验证。此漏洞的原因就是将DedeUserID
和DedeUserID__ckMd5
伪造成为了其他用户的值,这样就导致伪造其他用户的Cookie而能够以其他的用户登录。
漏洞的前提是要求开启前台用户注册的功能,保证用户能够顺利地进行注册(包括审核通过,能够查看个人空间)
首先需要明确用户登录时后台的处理流程。当会员登录时,后台的处理流程时:
在dedecms中,所有的cookie是成对出现的。如DedeUserID__ckMd5
和DedeUserID
,以及DedeLoginTime__ckMd5
和DedeLoginTime
。其中的DedeUserID__ckMd5
这种形式是对DedeUserID
进行了校验,防止前台的Cookie被任意地篡改。dedecms
中所有的cookie操作都是由include/helpers/cookie.helper.php:PutCookie()
方法设置的,如下:
$key.'__ckMd5'
是由substr(md5($cfg_cookie_encode.$value),0,16)
得到的,其中的cfg_cookie_encode
是在程序安装时设置的。所以如果我们不知道这个值,我们是无法破解$key.'__ckMd5'
。所以一般情况下,我们是无法破解$key.'__ckMd5'
这类值的。
所有的取Cookie的操作都是由include/helpers/cookie.helper.php:GetCookie()
设置的,如下:
由于根据$_COOKIE[$key . '__ckMd5']
对$_COOKIE[$key]
进行了校验,这种方式就能够有效地避免前台任意第修改Cookie,这种方式本意上是好的,但是由于dedecms的实现问题,导致这种方式也会成为漏洞的根源。试想一下,如果我们同时修改了$_COOKIE[$key]
和$_COOKIE[$key . '__ckMd5']
就可以绕过这个验证了。
include/memberlogin.class.php
中的MemberLogin
类
其中取得M_ID
的函数是$this->M_ID = $this->GetNum(GetCookie("DedeUserID"));
,其中GetNum()
的做法是:
直接将不是数字的字符全部都替换掉。之后通过SQL语句Select * From '#@__member' where mid='{$this->M_ID}'
来判断用户的身份。由于GetNum()
采用的是替换的方法,那么如果Cookie中的DedeUserID
是0000001,
正常情况下,程序中的DedeUserID
就是当前用户的用户ID。但是如果将其中的DedeUserID
和DedeUserID__ckMd5
都替换为其他的账户,是否就意味着我们可以以其他的账户登录呢?
那么现在的问题就在于,如何进行伪造呢?我们知道管理员admin
的mid是1,但是我们如何得到对应的DedeUserID__ckMd5
呢?所以目前的问题转变为我们需要得到1
所对应的DedeUserID__ckMd5
的。
分析member/index.php
中的122
行之后的代码:
对其中的last_vid
的赋值情况进行分析:
那么这就是存在一个漏洞的触发点。如果我们将$uid设置为001
,那么得到的last_vid
和last_vid__ckMd5
刚好就是一对可以使用的Cookie,同时001
也刚好可以绕过管理员的验证。
但是需要注意的是,在124行存在代码require_once(DEDEMEMBER.'/inc/config_space.php');
,引入了config_space.php
文件。跟进member/inc/config_space.php
,
在数据库查询的语句中where m.userid like '$uid' ORDER BY g.dtime DESC "
,其中传入的$uid
作为了userid
。如果不存在程序就会退出。那么这就意味着我们传入的uid
必须是已经存在的一个用户名,所以我们注册用户名时注册的用户名最好是001
这种。
至此,所有的漏洞触发点集合在一起就能够触发这个漏洞了。整个漏洞的触发流程是:
注册一个用户名为001
的用户
注册成功之后,跳转到会员首页(会员首页地址是member/index.php
)。可以发现此时还是正常用户001
,只有4个Cookie值,分别是DedeUserID__ckMd5
、DedeUserID
、DedeLoginTime__ckMd5
、DedeLoginTime
。
访问链接member/index.php?uid=001
。此时Cookie中会增加一些内容,其中最为重要的是last_vid
和last_vid__ckMd5
,取得他们的值。在我本地演示中,他们分别是001
和c6fda866fabaca07
最为关键的第一步,替换掉Cookie中的DedeUserID
为001
,DedeUserID__ckMd5
为第四步last_vid__ckMd5
得到的值c6fda866fabaca07
。
修改完毕之后,访问会员首页member/index.php
,此时已经变成了管理员了。
演示中主要是登录了管理员的身份。因为其中我们中间访问时利用的是member/index.php?uid=001
。如果需要登录其他账户的身份,比如id为7的用户,那么我们就需要注册一个名为007
的账户,同时中途需要访问的是member/index.php?uid=007
链接,将其中的last_vid
和last_vid__ckMd5
进行替换,就可以以007
的账户登录。以此类推,我们就可以以任意的账户进行登录了。
以上就完成了对整个漏洞的分析与复现。本漏洞其实主要是因为dedecms是通过Cookie中的DedeUserID
对用户身份进行鉴别的,虽然使用了DedeUserID__ckMd5
,但是我们看到还是可以被绕过的,最终形成了Cookie伪造导致任意用户登录的漏洞。
对于身份认证这类的信息,最好是放在Session中,前台的用户不容易修改,就不会出现这种Cookie伪造的问题了。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课