最近一年来很少写blog,主要原因是三个,一是一直以来在blog里说的,稍微带点技术的东西,因为可能未来在公司里用得上,所以不方便在blog上讲;二是最近转战微博了,也算是顺应潮流;三是最近一年多来,博文视点约我写了一本书,仅有的那么一点时间也要投入到写书大计中,实在没什么时间再写博客这种奢侈的东西。好在这本关于Web Security的书终于是快要写完了,在最近应该能够交稿,一共写了18章,第一次写书,没什么经验,还有很多不尽如人意的地方,但丑媳妇总得见公婆。

 

未来可能时间会稍微多一点,其实最主要的原因是我的工作重心会从纷乱的项目中抽出来,往技术研究方面做些偏移,所以这个blog会慢慢的恢复更新。今天就先丢个小玩意上来吧,shopex的这个漏洞有点意思,虽然shopex的代码安全质量很有问题,但我们在此只关注这个有意思的地方,出于和谐的考虑,原文的POC代码也和谐掉了,见谅。

 

我们知道弱伪随机数算法往往会带来很多安全问题,而程序员最容易犯的一个错误就是使用时间函数代替伪随机数算法,无形中使得本来容易犯错的随机数问题直接变成可预测的漏洞。

在shopex 4.8.5中,密码取回没有使用发送激活链接到邮箱的方式,而是直接生成一个新的密码发送到用户邮箱中。这个设计本身就不够安全,而新生成的密码算法是这样的:

/core/shop/controller/ctl.passport.php中:

 

function sendPSW(){

$this->begin($this->system->mkUrl('passport','lost'));

$member=&$this->system->loadModel('member/member');

$data=$member->getMemberByUser($_POST['uname']);

if(($data['pw_answer']!=$_POST['pw_answer']) || ($data['email']!=$_POST['email'])){

$this->end(false,__('问题回答错误或当前账户的邮箱填写错误'),$this->system->mkUrl('passport','lost'));

}

 

if( $data['member_id'] < 1 ){

$this->end(false,__('会员信息错误'),$this->system->mkUrl('passport','lost'));

}

 

$messenger = &$this->system->loadModel('system/messenger');echo microtime()."<br/>";

$passwd = substr(md5(print_r(microtime(),true)),0,6);

 

$pObj=$this->system->loadModel('member/passport');

if ($obj=$pObj->function_judge('edituser')){

$res = $obj->edituser($data['uname'],'',$passwd,$data['email'], '1');

if ($res>0){

$member->update(array('password'=>md5($passwd)),array('member_id'=>intval($data['member_id'])));

}

else{

trigger_error('输入的旧密码与原密码不符!', E_USER_ERROR);

return false;

}

}else{

$member->update(array('password'=>md5($passwd)),array('member_id'=>intval($data['member_id'])));

}

 

$data['passwd'] = $passwd;

$memberObj = &$this->system->loadModel('member/account');

$memberObj->fireEvent('lostPw',$data,$data['member_id']);

 

$this->end(true,__('邮件已经发送'),$this->system->mkUrl('passport','index'));

}

 

 

注意加粗的部分,新生成的用户密码实际上就是取了当前时间 microtime() 的md5值的前6位。

 

但PHP 中microtime()的值除了当前服务器的秒数外,还有微秒数,比如:

0.55452100 1315562338

微妙数的变化范围在0.000000 -- 0.999999 之间,一般来说,服务器的时间可以通过HTTP返回头的DATE字段来获取,因此我们只需要遍历这1000000可能值即可。

但我们要使用暴力破解的方式发起1000000次网络请求的话,网络请求数也会非常之大。可是shopex非常贴心的在生成密码前再次将microtime() 输出了一次:

$messenger = &$this->system->loadModel('system/messenger');echo microtime()."<br/>";

两次microtime()的调用间隔非常之短,使得我们破解的成本实际上非常低廉。至今仍然没有想明白shopex为什么要写这段代码,难道是开发留的后门?

 

要成功利用这个漏洞需要满足以下条件:

触发密码取回流程,这需要回答安全问题或者是用户注册邮箱填写正确;而shopex默认用户注册是只需要邮箱而不需要填写安全问题的

 

在自动化验证新密码是否正确时,需要自动登录网站,而shopex的登录默认便是有验证码的。可是这个验证码的实现存在缺陷:

if($_COOKIE["S_RANDOM_CODE"]!=md5($_POST['signupverifycode'])){

$this->splash('failed','back',__('验证码录入错误,请重新输入'),'','',$_POST['from_minipassport']);

}

验证码的验证,实际上是比对两个值,一个是Cookie S_RANDOM_CODE的值,另一个是验证码的md5,因此通过一个小trick就能够绕过shopex的验证码机制:提交验证码为1234,并自定义cookie值为md5("1234"),这个验证码的校验将永远有效。

 

结合上面这些,最终给出了我们的exploit:

 

[略]。。。。。。

 

测试如下:

 

D:researchvulndbshopex>python getpass.py target username email

(测试网站略)

 

在实际利用中,很多shopex小版本不返回微秒数,但能抓取到秒数。暴力破解微妙数的话,请求会过于频繁。在此不赘述了。

转载请注明来自WebShell'S Blog,本文地址:https://www.webshell.cc/1821.html