前言
这段时间忙于工作,无法自拔~~~
上周末刚好有空,随便逛了一下java开源cms,发现一个star挺多的mcms,就看了一下issues,发现了两个比较有意思的地方(主要感觉问题没有修复完全),写出来请大伙指点指点。
分析
毕竟水平太次,只能写点简单的东西了~~~
后台模版管理
该cms开发上使用了Freemarker框架,在一些历史版本中,也存在很多模版注入的问题。
![图片[1]-从mcms历史漏洞中捡漏-NGC660 安全实验室](http://ngc660.cn/wp-content/uploads/2022/09/d2b5ca33bd090928-1024x666.png)
这些问题大部分是通过后台模版管理模块可上传zip文件进行自解压或者可修改模版htm文件插入payload达到目的。
- 上传zip文件解压:net.mingsoft.basic.action.ManageFileAction#uploadTemplate:
![图片[2]-从mcms历史漏洞中捡漏-NGC660 安全实验室](http://ngc660.cn/wp-content/uploads/2022/09/d2b5ca33bd091112-1024x390.png)
![图片[3]-从mcms历史漏洞中捡漏-NGC660 安全实验室](http://ngc660.cn/wp-content/uploads/2022/09/d2b5ca33bd091121-1024x440.png)
跟进uploadTemplate:
public ResultData uploadTemplate(BaseFileAction.Config config) throws IOException {
String[] errorType = this.uploadFileDenied.split(",");
String fileName = config.getFile().getOriginalFilename();
if (fileName.lastIndexOf(".") < 0) {
this.LOG.info("文件格式错误:{}", fileName);
return ResultData.build().error(this.getResString("err.error", new String[]{this.getResString("file.name")}));
} else {
String fileType = fileName.substring(fileName.lastIndexOf("."));
boolean isReal = (new File(this.uploadTemplatePath)).isAbsolute();
String realPath = null;
if (!isReal) {
realPath = BasicUtil.getRealPath("");
} else {
realPath = this.uploadTemplatePath;
}
if (!config.isRename()) {
fileName = config.getFile().getOriginalFilename();
if (fileName.endsWith(".") && System.getProperty("os.name").startsWith("Windows")) {
this.LOG.info("文件类型被拒绝:{}", fileName);
return ResultData.build().error(this.getResString("err.error", new String[]{this.getResString("file.type")}));
}
fileType = fileName.substring(fileName.lastIndexOf("."));
} else {
fileName = System.currentTimeMillis() + fileType;
}
String[] var7 = errorType;
int var8 = errorType.length;
String path;
for(int var9 = 0; var9 < var8; ++var9) {
path = var7[var9];
if (fileType.equalsIgnoreCase(path)) {
this.LOG.info("文件类型被拒绝:{}", fileType);
return ResultData.build().error(this.getResString("err.error", new String[]{this.getResString("file.type")}));
}
}
String uploadFolder = realPath + File.separator;
if (StringUtils.isNotBlank(config.getUploadPath())) {
uploadFolder = uploadFolder + config.getUploadPath() + File.separator;
}
File saveFolder = new File(uploadFolder);
File saveFile = new File(uploadFolder, fileName);
if (!saveFolder.exists()) {
FileUtil.mkdir(saveFolder);
}
config.getFile().transferTo(saveFile);
path = uploadFolder.replace(realPath, "") + "/" + fileName;
return ResultData.build().success((new File("/" + path)).getPath().replace("\\", "/").replace("//", "/"));
}
}
这里有个黑名单的判断。通过String[] errorType = this.uploadFileDenied.split(“,”);和注解拿到黑名单
![图片[4]-从mcms历史漏洞中捡漏-NGC660 安全实验室](http://ngc660.cn/wp-content/uploads/2022/09/d2b5ca33bd091623-1024x419.png)
application.yml
![图片[5]-从mcms历史漏洞中捡漏-NGC660 安全实验室](http://ngc660.cn/wp-content/uploads/2022/09/d2b5ca33bd091640-1024x270.png)
不能为exe jsp jspx sh 等类型文件,然后通过unZip接口解压:
net.mingsoft.basic.action.TemplateAction#unZip:
![图片[6]-从mcms历史漏洞中捡漏-NGC660 安全实验室](http://ngc660.cn/wp-content/uploads/2022/09/d2b5ca33bd091701-1024x381.png)
![图片[7]-从mcms历史漏洞中捡漏-NGC660 安全实验室](http://ngc660.cn/wp-content/uploads/2022/09/d2b5ca33bd091708-1024x381.png)
这里主要手法就是上传包含payload的模板文件,然后设置进行引用。
- 修改模板文件:
同样后台提供了net.mingsoft.basic.action.TemplateAction#writeFileContent去写内容:
![图片[8]-从mcms历史漏洞中捡漏-NGC660 安全实验室](http://ngc660.cn/wp-content/uploads/2022/09/d2b5ca33bd091759-1024x335.png)
通过该接口,在一些历史版本中同样可以写入模板注入payload。
- 思考
到这里,如果是以tomcat部署war的形式,是否可以通过上传zip(war)自解压到上级目录getshell
在5.2.1版本中,进一步跟进net.mingsoft.basic.action.TemplateAction#unZip:
![图片[9]-从mcms历史漏洞中捡漏-NGC660 安全实验室](http://ngc660.cn/wp-content/uploads/2022/09/d2b5ca33bd091859-1024x401.png)
调试后发现最后会通过cn.hutool.core.io.FileUtil#checkSlip判断解压文件位置是否跳出父目录,我们通过../进行路径穿越的话显然是会报错:
![图片[10]-从mcms历史漏洞中捡漏-NGC660 安全实验室](http://ngc660.cn/wp-content/uploads/2022/09/d2b5ca33bd091917-1024x697.png)
这样看的话,这个整个应用中应该不存在这个问题,但是当我git下最新版本(5.2.8)后却发现这里的unzip没有调用该方法,那么说明在最新版本中是可以利用的。
private void unzip(File zipFile, String descDir) throws IOException {
ZipArchiveInputStream inputStream = new ZipArchiveInputStream(new BufferedInputStream(new FileInputStream(zipFile)));
File pathFile = new File(descDir);
if (!pathFile.exists()) {
pathFile.mkdirs();
}
ZipArchiveEntry entry = null;
while((entry = inputStream.getNextZipEntry()) != null) {
String[] dirs = entry.getName().split("/");
String tempDir = descDir;
String[] var8 = dirs;
int var9 = dirs.length;
for(int var10 = 0; var10 < var9; ++var10) {
String dir = var8[var10];
if (dir.indexOf(".") == -1) {
tempDir = tempDir + File.separator.concat(dir);
FileUtil.mkdir(tempDir);
}
}
if (entry.isDirectory()) {
File directory = new File(descDir, entry.getName());
directory.mkdirs();
} else {
BufferedOutputStream os = null;
try {
this.LOG.debug("file name => {}", entry.getName());
try {
os = new BufferedOutputStream(new FileOutputStream(new File(descDir, entry.getName())));
IOUtils.copy(inputStream, os);
} catch (FileNotFoundException var15) {
this.LOG.error("模版解压{}不存在", entry.getName());
var15.printStackTrace();
}
} finally {
IOUtils.closeQuietly(os);
}
}
}
}
将整个项目打包成war部署:
构造zip文件:
![图片[11]-从mcms历史漏洞中捡漏-NGC660 安全实验室](http://ngc660.cn/wp-content/uploads/2022/09/d2b5ca33bd092015-1024x463.png)
通过后台上传,成功解压到webapps目录:
![图片[12]-从mcms历史漏洞中捡漏-NGC660 安全实验室](http://ngc660.cn/wp-content/uploads/2022/09/d2b5ca33bd092043-1024x206.png)
getshell:
![图片[13]-从mcms历史漏洞中捡漏-NGC660 安全实验室](http://ngc660.cn/wp-content/uploads/2022/09/d2b5ca33bd092103-1024x347.png)
修改ueditor配置上传
5.2.1版本中存在net.mingsoft.basic.action.web.EditorAction#editor一个前台的接口:
![图片[14]-从mcms历史漏洞中捡漏-NGC660 安全实验室](http://ngc660.cn/wp-content/uploads/2022/09/d2b5ca33bd092130-1024x669.png)
跟进com.mingsoft.ueditor.MsUeditorActionEnter:
![图片[15]-从mcms历史漏洞中捡漏-NGC660 安全实验室](http://ngc660.cn/wp-content/uploads/2022/09/d2b5ca33bd092142-1024x704.png)
这个逻辑很简单,将我们传入jsonconfig写入获取的jsonobject中,这里的jsonobject实际上就是static/plugins/ueditor/1.4.3.3/jsp/config.json:
![图片[16]-从mcms历史漏洞中捡漏-NGC660 安全实验室](http://ngc660.cn/wp-content/uploads/2022/09/d2b5ca33bd092154-1024x476.png)
接着执行com.baidu.ueditor.ActionEnter#exec:
![图片[17]-从mcms历史漏洞中捡漏-NGC660 安全实验室](http://ngc660.cn/wp-content/uploads/2022/09/d2b5ca33bd092209-1024x318.png)
跳到com.baidu.ueditor.ActionEnter#invoke:
![图片[18]-从mcms历史漏洞中捡漏-NGC660 安全实验室](http://ngc660.cn/wp-content/uploads/2022/09/d2b5ca33bd092221-1024x610.png)
这里this.actionType通过传参action得到,可以控制要执行的动作:
![图片[19]-从mcms历史漏洞中捡漏-NGC660 安全实验室](http://ngc660.cn/wp-content/uploads/2022/09/d2b5ca33bd092235-1024x349.png)
actioncode为1、2、3、4对应的都是上传:
![图片[20]-从mcms历史漏洞中捡漏-NGC660 安全实验室](http://ngc660.cn/wp-content/uploads/2022/09/d2b5ca33bd092247-1001x1024.png)
跟进case:
![图片[21]-从mcms历史漏洞中捡漏-NGC660 安全实验室](http://ngc660.cn/wp-content/uploads/2022/09/d2b5ca33bd092259-1024x651.png)
conf = this.configManager.getConfig(actionCode);//获取配置 state = (new Uploader(this.request, conf)).doExec();//执行上传
com.baidu.ueditor.ConfigManager#getConfig:
![图片[22]-从mcms历史漏洞中捡漏-NGC660 安全实验室](http://ngc660.cn/wp-content/uploads/2022/09/d2b5ca33bd092315-1024x392.png)
看到这里,基本上就可以干很多事了,那么新版本中也是进行了修复:
![图片[23]-从mcms历史漏洞中捡漏-NGC660 安全实验室](http://ngc660.cn/wp-content/uploads/2022/09/d2b5ca33bd092325-1024x585.png)
使用cn.hutool.core.io.FileUtil#normalize对三个路径进行了修复。但是上传的文件后缀还是可以控制的,在特殊情况下也可以通过jspx而getshell。(至于为什么只对路径进行修复,我想应该是该issue提交的师傅原来利用该缺陷通过修改上传地址上传了一个包含freemarker的payload从而进行模板执行,那么估计人家想的就是控制路径了吧)
在项目(最新版本)以war包部署在tomcat时:
![图片[24]-从mcms历史漏洞中捡漏-NGC660 安全实验室](http://ngc660.cn/wp-content/uploads/2022/09/d2b5ca33bd092338-1024x394.png)
![图片[25]-从mcms历史漏洞中捡漏-NGC660 安全实验室](http://ngc660.cn/wp-content/uploads/2022/09/d2b5ca33bd092354-1024x637.png)
总结
怎么说呢,不管怎样,还是学习比上班要快乐~~~~
参考
https://gitee.com/mingSoft/MCMS/issues/I4QZ1O
https://gitee.com/mingSoft/MCMS/issues/I4Q4NV
本文作者:是juju呀
本文转载于先知社区,如有侵权请联系删除
昶之琴![资深玩家 徽章-资深玩家-NGC660 安全实验室](http://ngc660.cn/wp-content/themes/zibll/img/thumbnail-null.svg)
2年前0
我以为只要提交就行了好想告诉你![备受喜爱 徽章-备受喜爱-NGC660 安全实验室](http://ngc660.cn/wp-content/themes/zibll/img/thumbnail-null.svg)
2年前0
花巨资看一下