通过uploaderOperate.jsp上传,OfficeServer移动实现未授权任意文件上传getshell。
0x01 OfficeServer接口
接口对应的class为OfficeServer.class
,将其反编译,看到代码如下:
(1)首先判断请求方法为POST
(2)然后将this.MsgObj.sendType
设置为JSON格式
(3)调用this.MsgObj.Load
解析参数,跟进该方法
该方法有两种解析参数的方法,根据函数名可以得知,一种是处理表单数据,另一种是处理文件上传数据,这里我们先看处理表单数据的,因为还没涉及到文件。
从上传的表单中,将数据转化为json格式,并赋值给saveFormParam
属性。
(4)获取上传表单的数据,并根据数据执行对应功能
(5)当mOption
为INSERTIMAGE
时,存在漏洞,我们直接到该循环中,如下:
当传入的参数isInsertImageNew
为1时,进入到插入图片的功能,具体实现如下:
(6)根据传入的imagefileid4pic
,从数据库查询文件名,如果文件名中存在.
则以原后缀名作为后缀,否则以.jpg
作为后缀。
(7)获取文件内容并且保存
ImageFileManager.getInputStreamById
方法会根据传入的imagefileid4pic
从数据库中获取其真实路径,然后读取文件流,返回inputStream
对象,如下:
isZip
在ecology默认安装的时候为1,如果用户修改设置,不进行压缩时,会直接打开文件,获取文件流。
然后跟进OdocFileUtil.getFileFromByte
,如下:
其中var1
为GCONST.getRootPath()
,也就是网站根目录。var2
为(6)中计算的文件名。
到这也就实现,任意的文件写入了,但是现在我们需要一个接口,可以将文件上传到服务器,并且写入ecology..iamgefile
这个表中。
0x02 uploaderOperate接口任意文件上传
接口位置:workrelate/plan/util/uploaderOperate.jsp
当传入的secId不为0的时候,调用dev.uploadDocToImg方法,接着往上看一下dev是哪个对象。
找到对应的方法,如下:
直接跳过这一对创建对象,赋值,if条件判断等内容,因为都没有return exit等操作,直接找到下面的关键点(代码位于:classbean/weaver/docs/docs/DocExtUtil.class
),如下:
流程如下:
- 1.创建了RecordSet类,是用来操作数据库的,后面要用到
RecordSet var19 = new RecordSet();
- 2.获取了当前数据库中存放的文档id的下一位值
int var20 = var12.getNextDocId(var19);
- 3.调用上传函数
String var21 = var1.uploadFiles(var3); //var3为Filedata
此处传入的值var3
为Filedata
接着跟进到classbean/weaver/file/FileUpload.class
当中,找到uploadFiles
的实现,如下:
然后会调用
该方法中验证this.mpdata
是否为空,这里不需要担心,在实例化对象的时候,会给赋值,只需要满足带有上传附件即可,如下:
跟进getAttachment
方法,可以看到:
其返回了一个MultipartRequest
对象,然后在实例化该对象时,会将文件压缩写入到filesystem
目录下,如下:
FilePart.writeTo
的核心代码:
到这里我们大概清楚,当文件上传时,会将临时文件以zip的形式保存在D:/WEAVER/ecology/filesystem/
接着回到uploadFiles(String[] var1, String var2)
,继续跟进逻辑:
- 先遍历上传文件数组,清除文件名中xss相关payload
- 判断上传文件数组是否为空
- 判断this.getParameter("name") 是否不为空
- 保存文件
int var3 = var1.length; // 因为上一个函数将文件名处理为数组,传入该函数
String[] var4 = new String[var3];
this.filenames = new String[var3];
for(int var5 = 0; var5 < var3; ++var5) { // 遍历var1数组,也就是附件名 ['Filedata']
this.filenames[var5] = SecurityMethodUtil.textXssClean(this.mpdata.getOriginalFileName(var1[var5])); // xss清除
if (this.filenames[var5] == null || "".equals(this.filenames[var5])) { // 判断请求中是否未传入文件
return var4;
}
// var2来源于:this.getParameter("name") 函数,只要不传入name参数,var2即为False
// 所以!StringUtils.isBlank(var2)为False,整个if条件为False
if (!StringUtils.isBlank(var2) && !var2.equals(this.filenames[var5]) && (var2.equals(this.filenames[var5]) || "file".equals(this.filenames[var5]))) {
this.filenames[var5] = var2;
var4[var5] = this.saveFile(var1[var5], var2, this.mpdata);
} else {
// 到这里,调用saveFile保存文件
var4[var5] = this.saveFile(var1[var5], this.mpdata);
}
}
跟进到saveFile
当中,看一下逻辑实现:
1、获取文件保存路径:
String var4 = var2.getFilePath(var1); //var4 = D:/WEAVER/ecology/filesystem/
2、获取文件名
String var5 = var2.getFileName(var1); //临时文件名
// 原始文件名
String var6 = SecurityMethodUtil.textXssClean(var2.getOriginalFileName(var1));
String var7 = var2.getContentType(var1);
long var8 = var2.getFileSize(var1);
String var10 = Util.null2String(this.getParameter("imagefilename"));
String var11 = Util.null2String(var6);
String var12 = var11.contains(".") ? var11.substring(var11.indexOf(".")) : "";
// imagefilename不为空,且后缀与原始文件名一致时,优先取imagefilename作为文件名
if (!var10.isEmpty() && ("".equals(var12) || var10.endsWith(var12))) {
var6 = var10;
}
3、进行大小判断和是否允许此类后缀上传的判断
默认配置,任意文件都可以上传。所以,会直接进入下一个else:
首先判断,上传内容是否需要压缩,代码如下:
如果我们不传入needCompressionPic
则会直接进入下面,其中this.needzip
和this.needzipencrypt
存放于数据库SystemSet
中,两者默认均为1
然后继续跟进,代码如下:
其逻辑如下:
1.生成插入imagefile表的数据库语句
2.创建OSS对象生成对应的aescode和Tokenkey
3.更新数据库,将文件名等信息写入imagefile
数据库
4.上传文件到OSS
到这里,我们就完成了文件的上传,而且imageFilename
为.jsp
格式,然后将imagefileid
传给OfficeServer
接口即可解压文件,getshell。
0x03 漏洞利用
pocsuite -u http://127.0.0.1:8082/ -r ~/exp/poc/ecology/ecology_upload_rce_nday.py
- 本文作者: Alivin
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/1782
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!