2012年(272)
分类: 网络与安全
2012-06-27 16:16:09
最近一年来很少写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()."
";
$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()."
";
两次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:\research\vulndb\shopex>python getpass.py target
username email
(测试网站略)
在实际利用中,很多shopex小版本不返回微秒数,但能抓取到秒数。暴力破解微妙数的话,请求会过于频繁。在此不赘述了。
PS:此漏洞一月前已提交wooyun