国内大部分带登录注册功能的PHPCMS模板都有邮箱找回密码功能,前几天我从先知的厂商类型分类中选择一个流行厂商和一般厂商来观察它们各自的实现过程,发现只要存在一个任意读漏洞,都有可能利用邮箱密码找回过程重置任意用户的密码。废话不多说,先上代码。
它比较典型,XXCMS_Home_Setup_v4.2.26涉及到密码重置的方法有两个,分别是位于/upload/Application/Home/Controller/MembersController.class.php
里的MembersController::user_getpass
和MembersController::user_setpass
。
user_getpass
方法中负责生成重置密码链接的相关代码如下
密码重置链接中的关键参数key即$str
通过下列方式得出
$str = encrypt(http_build_query(array('e'=>$email,'k'=>$key,'t'=>$time)),C('PWDHASH'));
encrypt
方法是XXCMS本身的加密函数,'e'=>$email
是我们在重置密码表单中提交的帐号邮箱,可控。'k'=>$key
是由上一步对输入邮箱和当前时间md5
后取第8
位开始的16位字符串,邮箱是可控,代码执行时间不可控。C('PWDHASH')
是从配置文件/upload/Application/Common/Conf/pwdhash.php
取一个作用类似盐的字符串,这个文件在我本地测试环境中内容如下
在你安装XXCMS完成之前PWDHASH
后面的字符串是空的,它是在安装过程中生成的一个随机字符串,之后不再变化。这个文件引出了本文的目的,如果有一个任意读漏洞,能够读取这个字符串,结合之前密码找回链接中的参数,除了时间不可控,我们是否可以自己构造密码重置链接?而且,时间一般是用来验证密码重置链接的有效期,所以这个变量理论上本身就可控。
来验证一下上面的猜想,再看user_setpass
方法中负责校的相关代码
首先从_GET参数中获取key
参数,解密,结果放入$data
中
parse_str(decrypt(I('get.key','','trim'),C('PWDHASH')),$data);
解密时仍然把C('PWDHASH')
作为参数传入,decrypt
方法是和encrypt
方法对应的解密方法。后面三行的代码,首先校验解密出的结果是否包含关键字email
,然后校验解密出来的时间加上24小时是否小于当前时间,即重置密码链接的有效期。到此我们就可以判断出,如果能通过任意读漏洞获取PWDHASH
内容,我们只要在程序执行完user_getpass
之后的24小时内,就可以自己构造能通过后台校验的重置密码链接,任意重置任意邮箱绑定的帐号。
测试数据
用户名:test123
邮箱:[email protected]
密码:111111
测试数据
用户名:test123
邮箱:[email protected]
密码:111111
后台开启smtp,否则无法使用密码找回功能,添加完后可测试是否成功
此外,由于cms本身没有打开注册后验证邮箱功能,在此手动打开
改为1
在/upload/Application/Home/Controller/
目录下放入TestController.class.php
,内容如下:
说明一下,这里为了方便直接放在XXCMS目录下,也可以把里面用到的encrypt
函数以及其他需要的库函数拿出来单独写一个php。C('PWDHASH')
是从配置文件中取PWDHASH
,位置在/upload/Application/Common/Conf/pwdhash.php
,C
函数的功能是读写配置,U
函数的功能是生成链接,实际上就是一个字符串拼接的过程。
准备工作到此结束
下面开始验证,首先选择登录页面的“找回密码”,输入测试帐号[email protected]
user_getpass
函数执行完毕。
打开我们准备的代码生成重置密码链接
在浏览器中打开该链接
出现修改密码密码的页面!
比如我们输入新密码222222
可以在数据库中看到密码hash值已经发生变化
最后再说说某CMS的密码找回过程。CMS存在同样的问题,但是没那么好利用。
它的邮箱找回密码链接中的关键参数code
可以看到这个里面没像XXCMS一样用提交邮箱来作为加密因子,刚看到这时我还在心里默默给CMS点了个赞,毕竟userid
是数据库中的内容,没有其他漏洞攻击者是不可能知道的。然鹅。。。
解密里居然只用is_numeric
判断userid
是否为数字,所以和上面XXCMS一样的原理,不过用起来可能存在一个问题,你用A账号的邮箱去申请重置,但是你自己生成密码找回链接时不知道A账号的userid
只能随便选一个正整数X(可能是B账号的userid),然后你用生成的链接放到浏览器中就可以重置B账号的密码,想想过程都觉得好蠢,就不贴POC了。