前言
今天看了一些文章,看到了一个验证码复用的漏洞,于是找了存在这个漏洞并且比较老的一款博客系统Emlog
复现
有三个验证码的地方
首先需要打开验证码功能,登陆后台后设置验证码
开启后,再到后台登陆页面
输入账号密码验证码,然后抓包
直接废包,不要让这个包传过去,导致页面刷新,刷新验证码
发到爆破模块,测试六位数字密码(原设置密码为123456)
不知道为啥,太老的原因吗,回显的有点慢,所以设置了比较接近的值
可以看到无需检验验证码,验证码一直是正确的,可以直接绕过去爆破密码
漏洞分析
来到后台登陆的验证页面
/admin/globals.php
获取post字段用户名user,密码pw,图片验证码imgcode,前提是开启验证码模式。ispersis字段不用管,验证函数中不需要。
获取完后进行验证,跟进checkUser函数
用户名密码不为空就验证账户名密码和$_SESSION['code']
首先判断验证码和$_SESSION['code']是否相同,这里再看生成验证码页面
/include/lib/loginauth.php
上面就是生成的随机验证码字符串,然后传入$_SESSION['code'],然后回到checkUser函数,判断完post传值的验证码和session中保存的验证码字符串是否相同,如果相同,在判断username,password,等到判断完password,返回true
然后回到最开始的/admin/globals.php
当true就经过一个跳转进入后台
如果上述的验证环境有问题,就会返回类似self::LOGIN_ERROR_AUTHCODE,对于是验证码错误,用户名错误,还是密码错误的标识,然后就来到loginPage函数,继续跟进
可以看到如果出现某种错误是直接进入loginPage()函数,然后向客户端抛出xx错误,请重新输入
分析完上面的验证的一系列代码,可以发现在验证结束,抛出对应错误的阶段,并没有对session['code']进行销毁。
利用流程就是在进入checkUser后,判断验证码通过了,但是密码错误,抛出密码错误,但是只要不去访问生成验证码的脚本,就不会刷新验证码,也不会刷新$_SESSION['code'],$_SESSION['code']保存的还是之前访问页面时候的验证码。
抓包后手动废包,然后放入爆破模块,验证码不变,爆破后台密码。(废包的原因:不让他去发送这个验证码,如果发送出去会让浏览器刷新,也刷新了验证码)
修复方法
/admin/globals.php页面
每判断一次,如果抛出错误就销毁$_SESSION['code'],如果还是按照之前的方法,只要出现密码错误一次后,自动销毁了$_SESSION['code'],
疑问
昨晚睡前又想了下,虽然销毁了$_SESSION['code'],如果第一次验证码错误后,销毁了session保存的验证码,那下一次如果将imgcode置空,作为空字符,与$_SESSION['code']对比,因为是!=,不是!==,所以只是值的对比,如果传入null,false,0也许都可以。但是早上起来调试发现,这里漏看了一笔,有一个empty的一个判断,会认为0是空字符传判断为true,而且字符串0和空字符也不想等。null和false字符串也与空字符不相等。
所以只要销毁了$_SESSION['code'],控制时效性,就可以修复了。
参考链接:
注:这个最早是P神在乌云上公布的漏洞,有兴趣的可以去看乌云漏洞库中的=》emlog验证码重用漏洞(密码爆破等影响)