【CVE-2024-32113】利用cursor分析apache ofbiz目录遍历绕过登录授权到代码执行漏洞
151****8729 发表于 广东 AI专栏 1645浏览 · 2024-10-27 07:20

cursor作为AI驱动的代码生成神奇,目前在国外已经是非常流行了。已经开始有一部分人开始尝试用cursor完全的接替原本的代码工作。

本文旨在介绍如果利用cursor去解读代码并分析漏洞。当我们更加深入了解cursor时,会被它强大的功能所震撼。

首先cursor无法直接帮我们去分析整个漏洞的漏洞原理和利用链,cursor的作用是辅助我们去分析这个漏洞。

我们需要自己很清楚要做什么,然后把一步步拆分得很细给到cursor去分析,借助cursor我们可以很快的分析并得到我们想要的答案。

以18.12.12为例,官方的漏洞修复补丁的文档在:

[OFBIZ-13006] [SECURITY] (CVE-2024-32113) Path traversal leading to RCE - ASF JIRA

补丁修复在这里:

提问:

  1. 为什么目录遍历能导致权限绕过
  2. 怎么触发代码执行

以下问题均是个人的提问 + cursor的回答。

0x01 提问一

主要目录结构

1、 framework/ - 框架核心目录

- **base/** - 基础组件
- **catalina/** - Tomcat/Catalina 相关配置
- **common/** - 通用功能组件
- **entity/** - 实体/数据模型相关
- **service/** - 服务层组件
- **webapp/** - Web应用组件
- **webtools/** - Web工具组件
- **start/** - 启动相关代码

关键配置文件

1、 组件配置

  • ofbiz-component.xml - 每个组件的配置文件,定义了组件的基本信息、资源加载器等
  • component-load.xml - 定义组件加载顺序
    2、Web相关配置
  • web.xml - Web应用配置文件
  • controller.xml - Web控制器配置
  • entityengine.xml - 实体引擎配置

特点分析

1、 模块化设计

  • 每个功能模块都是独立的组件
  • 通过 component-load.xml 控制加载顺序
<load-component component-location="base"/>
    <load-component component-location="entity"/>
    <load-component component-location="security"/>
    <load-component component-location="datafile"/>

2、 MVC架构

  • webapp 模块处理Web层
  • entity 模块处理数据层
  • service 模块处理业务逻辑层
    3、 配置驱动
  • 大量使用XML配置文件
  • 支持热部署和动态加载
    4、 扩展性
  • 插件化架构
  • 组件可以独立开发和部署

这是一个典型的Java企业级应用框架,采用组件化设计,具有良好的可扩展性和维护性。每个组件都有明确的职责划分,通过配置文件进行灵活组装。

0x02 提问二

让我帮你解析一下OFBiz中的Web路由控制机制:

1. 路由控制的核心组件

1、 ControlFilter - 请求过滤器

  • 负责URL白名单过滤
  • 处理重定向逻辑
  • 安全检查
    2、 ControlServlet - 主控制器
  • 处理所有Web请求的核心Servlet
  • 通过web.xml中的配置进行URL映射

2. 路由定义方式

1、 XML配置方式

<request-map uri="view">
        <security https="true" auth="false"/>
        <response name="success" type="view" value="main"/>
    </request-map>

    <request-map uri="chain">
        <event type="java" path="org.apache.ofbiz.webapp.event.TestEvent" invoke="test"/>
        <response name="success" type="request" value="/view"/>
        <response name="error" type="view" value="error"/>
    </request-map>

    <!-- End Utility Requests -->

    <request-map uri="main">
        <security https="true" auth="false"/>
        <response name="success" type="view" value="main"/>
    </request-map>

这个示例展示了如何在controller.xml中定义请求映射:

  • request-map 定义URL路径
  • security 定义安全要求
  • response 定义响应行为
    2、 URL路径控制
<servlet-mapping>
        <servlet-name>ControlServlet</servlet-name>

        <url-pattern>/control/*</url-pattern>

    </servlet-mapping>

所有请求都通过ControlServlet进行处理,基本格式为:

  • /control/* - 所有控制请求的基础路径
    3、 组件挂载点定义
<webapp name="webtools"
        title="WebTools"
        position="12"
        server="default-server"
        location="webapp/webtools"
        base-permission="OFBTOOLS,WEBTOOLS"
        mount-point="/webtools"/>

每个webapp组件需要定义:

  • name - 组件名称
  • mount-point - URL挂载点
  • location - 物理位置
  • base-permission - 基础权限要求

3. 请求处理流程

1、 请求首先经过ControlFilter:

  • 检查URL是否在允许列表中
  • 处理重定向逻辑
  • 执行安全检查
    2、 然后由ControlServlet处理:
  • 解析请求URL
  • 查找对应的request-map定义
  • 执行相应的处理逻辑
  • 返回定义的响应

4. 安全控制

路由定义中包含多层安全控制:

  • URL白名单过滤
  • 基于权限的访问控制
  • HTTPS要求
  • 认证要求

这种设计提供了:

  • 集中式的请求处理
  • 声明式的路由配置
  • 灵活的安全控制
  • 统一的请求处理流程

0x03 提问三

让我帮你分析这个请求的处理流程:

1. 请求入口

当访问 https://127.0.0.1:8443/webtools/control/main 时,请求会按以下顺序处理:

1、 首先经过 web.xml 中配置的过滤器链

<filter>
        <display-name>ControlFilter</display-name>

        <filter-name>ControlFilter</filter-name>

        <filter-class>org.apache.ofbiz.webapp.control.ControlFilter</filter-class>

        <init-param>
            <param-name>allowedPaths</param-name>

            <param-value>/ping.txt:/error:/control:/select:/index.html:/index.jsp:/default.html:/default.jsp</param-value>

        </init-param>

        <init-param>
            <param-name>redirectPath</param-name>

            <param-value>/control/main</param-value>

        </init-param>

    </filter>

    <filter>
        <display-name>CacheFilter</display-name>

        <filter-name>CacheFilter</filter-name>

        <filter-class>org.apache.ofbiz.base.util.CacheFilter</filter-class>

    </filter>

    <filter>
        <display-name>ContextFilter</display-name>

        <filter-name>ContextFilter</filter-name>

        <filter-class>org.apache.ofbiz.webapp.control.ContextFilter</filter-class>

    </filter>

    <filter>
        <display-name>SameSiteFilter</display-name>

        <filter-name>SameSiteFilter</filter-name>

        <filter-class>org.apache.ofbiz.webapp.control.SameSiteFilter</filter-class>

    </filter>

    <filter-mapping>
        <filter-name>ControlFilter</filter-name>

        <url-pattern>/*</url-pattern>

    </filter-mapping>

    <filter-mapping>
        <filter-name>CacheFilter</filter-name>

        <url-pattern>/*</url-pattern>

    </filter-mapping>

    <filter-mapping>
        <filter-name>ContextFilter</filter-name>

        <url-pattern>/*</url-pattern>

    </filter-mapping>

    <filter-mapping>
        <filter-name>SameSiteFilter</filter-name>

        <url-pattern>/*</url-pattern>

    </filter-mapping>

2、 然后到达 ControlServlet

<servlet>
        <description>Main Control Servlet</description>

        <display-name>ControlServlet</display-name>

        <servlet-name>ControlServlet</servlet-name>

        <servlet-class>org.apache.ofbiz.webapp.control.ControlServlet</servlet-class>

        <load-on-startup>1</load-on-startup>

    </servlet>

    <servlet-mapping>
        <servlet-name>ControlServlet</servlet-name>

        <url-pattern>/control/*</url-pattern>

    </servlet-mapping>

2. 权限验证流程

1、 ControlFilter 进行第一层验证

  • 检查请求路径是否在允许列表中
  • /control/main 在允许列表中:
<param-name>allowedPaths</param-name>

            <param-value>/ping.txt:/error:/control:/select:/index.html:/index.jsp:/default.html:/default.jsp</param-value>

        </init-param>

2、 检查登录状态和权限

GenericValue userLogin = (GenericValue) httpRequest.getSession().getAttribute("userLogin");
            if (!LoginWorker.hasBasePermission(userLogin, httpRequest)) { // Allows UEL and FlexibleString (OFBIZ-12602)
                if (!GenericValue.getStackTraceAsString().contains("ControlFilterTests")
                        && null == System.getProperty("SolrDispatchFilter") // Allows Solr tests
                        && SecurityUtil.containsFreemarkerInterpolation(httpRequest, httpResponse, requestUri)) {
                    return;
                }
            }

3、 controller.xml 中的权限配置

<request-map uri="main">
        <security https="true" auth="false"/>
        <response name="success" type="view" value="main"/>
    </request-map>

3. 结论

对于 /webtools/control/main 这个接口:

1、 是否需要登录:

  • 根据 controller.xml 中的配置 auth="false",这个接口不需要登录就可以访问
    2、 安全要求:
  • 需要 HTTPS:https="true"
  • 在白名单中:/control 在 allowedPaths 列表中
    3、 处理流程:
  • 请求首先经过 ControlFilter 的路径验证
  • 然后由 ControlServlet 处理具体的业务逻辑
  • 最后根据 controller.xml 的配置返回 "main" 视图

所以这是一个公开的入口页面,不需要登录授权,但必须使用 HTTPS 访问。

0x04 提问四

让我分析一下这个安全漏洞(OFBIZ-13006):

漏洞描述

这是一个URL规范化绕过漏洞。在修复前的代码中:

// normalize to remove ".." special name usage to bypass webapp filter
            try {
                requestUri = new URI(requestUri).normalize().toString();
            } catch (URISyntaxException e) {
                throw new RuntimeException(e);
            }

只对请求的URI进行了简单的normalize()处理,但没有对比规范化前后的URL是否一致。攻击者可以利用这个漏洞构造特殊的URL来绕过安全检查。

漏洞利用方式

攻击者可以通过以下方式绕过URL白名单检查:

1、 路径遍历攻击:

/webtools/control/../../../etc/passwd

2、 URL编码绕过:

/webtools/control/%2e%2e/%2e%2e/sensitive-data

3、 特殊字符组合:

/webtools/control/./././../../../confidential/

官方修复方案

新版本增加了额外的URL验证逻辑:

if (req.getRequestURL() != null) { // Allow tests with Mockito, ControlFilterTests
    String url = new URI(req.getRequestURL().toString()).normalize().toString();
    if (!req.getRequestURL().toString().equals(url)) {
        throw new RuntimeException();
    }
}

主要修复点:

  1. 获取完整的RequestURL而不仅仅是URI
  2. 对规范化前后的URL进行严格比对
  3. 如果不一致则直接抛出异常

安全建议

  1. 升级到最新版本
  2. 确保所有URL处理都经过严格的规范化和验证
  3. 实施严格的白名单机制
  4. 避免直接使用未经处理的用户输入作为文件路径

这是一个典型的输入验证不充分导致的安全漏洞,通过增加URL规范化验证得到了修复。

0x05 提问五

是的,这个漏洞确实可能被用来绕过登录授权。让我分析一下:

1、 漏洞存在的位置

// normalize to remove ".." special name usage to bypass webapp filter
            try {
                requestUri = new URI(requestUri).normalize().toString();
            } catch (URISyntaxException e) {
                throw new RuntimeException(e);
            }

这段代码只是简单地对URL进行normalize()处理,但没有验证normalize前后的URL是否一致。

2、 攻击场景分析

  • 假设存在一个需要授权的接口 /webtools/control/entitymaint
  • 在controller.xml中的配置:
<event type="service-stream" invoke="serviceStreamTest"/>
        <response name="success" type="none"/>
        <response name="error" type="none"/>
    </request-map>

要求auth="true",即需要登录。

3、 攻击路径

  • 攻击者可以构造URL:/webtools/control/main/../entitymaint
  • main接口配置为auth="false"
  • 经过normalize()后变成/webtools/control/entitymaint
  • 由于normalize()前的URL以/control/main开头,会被allowedPaths白名单放行
  • 最终可能访问到需要授权的entitymaint接口
    4、 漏洞原因
  • URL规范化检查不完整
  • 白名单验证在规范化之后
  • 没有验证规范化前后URL的一致性
    5、 影响范围
  • 可能绕过任何需要授权的接口
  • 可能访问到受保护的资源
  • 可能导致权限提升

这就是为什么官方在后续版本中增加了对比规范化前后URL是否一致的检查,以修复这个安全漏洞。

至此,我们解决了第一个问题,为什么目录遍历可以绕过登录授权

接下来我们引导cursor帮我们解决第二个问题,怎么让利用这个漏洞造成rce。

0x06 提问六

让我帮你分析一下ProgramExport这个接口的功能:

从controller.xml中可以看到,ProgramExport是一个视图映射(view-map),它指向了component://webtools/widget/EntityScreens.xml中的ProgramExport屏幕定义。

通过查看相关代码,这个接口主要实现了以下功能:

1、 它是一个实体(Entity)导出工具,允许用户导出实体定义为EOModel格式
2、 从WebToolsServices.java中可以看到具体实现:

return ServiceUtil.returnError(UtilProperties.getMessage(resource, "WebtoolsEomodelFullPathIsNotWriteable", UtilMisc.toMap("eomodeldFullPath", eomodeldFullPath), locale));
            }

            Set<String> entityNames = new TreeSet<String>();
            if (UtilValidate.isNotEmpty(entityPackageNameOrig)) {
                Set<String> entityPackageNameSet = new HashSet<String>();
                entityPackageNameSet.addAll(StringUtil.split(entityPackageNameOrig, ","));

                Debug.logInfo("Exporting with entityPackageNameSet: " + entityPackageNameSet, module);

                Map<String, TreeSet<String>> entitiesByPackage = reader.getEntitiesByPackage(entityPackageNameSet, null);
                for (Map.Entry<String, TreeSet<String>> entitiesByPackageMapEntry: entitiesByPackage.entrySet()) {
                    entityNames.addAll(entitiesByPackageMapEntry.getValue());
                }
            } else if (UtilValidate.isNotEmpty(entityGroupId)) {
                Debug.logInfo("Exporting entites from the Group: " + entityGroupId, module);
                entityNames.addAll(EntityGroupUtil.getEntityNamesByGroup(entityGroupId, dctx.getDelegator(), false));
            } else {
                entityNames.addAll(reader.getEntityNames());
            }
            Debug.logInfo("Exporting the following entities: " + entityNames, module);

            // remove all view-entity
            Iterator<String> filterEntityNameIter = entityNames.iterator();
            while (filterEntityNameIter.hasNext()) {
                String entityName = filterEntityNameIter.next();
                ModelEntity modelEntity = reader.getModelEntity(entityName);
                if (modelEntity instanceof ModelViewEntity) {
                    filterEntityNameIter.remove();
                }
            }

            // write the index.eomodeld file
            Map<String, Object> topLevelMap = new HashMap<String, Object>();
            topLevelMap.put("EOModelVersion", "\"2.1\"");
            List<Map<String, Object>> entitiesMapList = new LinkedList<Map<String,Object>>();
            topLevelMap.put("entities", entitiesMapList);
            for (String entityName: entityNames) {
                Map<String, Object> entitiesMap = new HashMap<String, Object>();
                entitiesMapList.add(entitiesMap);
                entitiesMap.put("className", "EOGenericRecord");
                entitiesMap.put("name", entityName);
            }
            UtilPlist.writePlistFile(topLevelMap, eomodeldFullPath, "index.eomodeld", true);

            // write each <EntityName>.plist file
            for (String curEntityName: entityNames) {
                ModelEntity modelEntity = reader.getModelEntity(curEntityName);
                UtilPlist.writePlistFile(modelEntity.createEoModelMap(entityNamePrefix, datasourceName, entityNames, reader), eomodeldFullPath, curEntityName +".plist", true);
            }
            Integer entityNamesSize = entityNames.size();
            return ServiceUtil.returnSuccess(UtilProperties.getMessage(resource, "WebtoolsEomodelExported", UtilMisc.toMap("entityNamesSize", entityNamesSize.toString(), "eomodeldFullPath", eomodeldFullPath), locale));
        } catch (UnsupportedEncodingException e) {
            return ServiceUtil.returnError(UtilProperties.getMessage(resource, "WebtoolsEomodelSavingFileError", UtilMisc.toMap("errorString", e.toString()), locale));
        } catch (FileNotFoundException e) {
            return ServiceUtil.returnError(UtilProperties.getMessage(resource, "WebtoolsEomodelFileOrDirectoryNotFound", UtilMisc.toMap("errorString", e.toString()), locale));
        } catch (GenericEntityException e) {
            return ServiceUtil.returnError(UtilProperties.getMessage(resource, "WebtoolsEomodelErrorGettingEntityNames", UtilMisc.toMap("errorString", e.toString()), locale));
        }
    }

主要功能包括:

  • 允许用户指定导出路径(eomodeldFullPath)
  • 可以选择性导出指定的实体包(entityPackageNameOrig)或实体组(entityGroupId)
  • 会过滤掉视图实体(view-entity)
  • 将实体定义导出为EOModel格式,包括:
    • 生成index.eomodeld文件
    • 为每个实体生成对应的.plist文件
    • 包含实体的属性、关系等信息
      3、 这个功能主要用于:
  • 实体建模和设计
  • 系统集成
  • 数据结构文档化
    4、 安全性:
    从controller.xml中可以看到这个接口需要认证:
<security https="true" auth="true"/>

所以这是一个用于导出OFBiz实体定义的管理工具接口,主要面向开发和系统管理人员使用。

0x07 提问七

让我帮你分析一下ProgramExport接口的代码逻辑:

1、 路由配置:
在controller.xml中定义了ProgramExport的视图映射:

<view-map name="ProgramExport" type="screen" page="component://webtools/widget/EntityScreens.xml#ProgramExport"/>

2、 安全控制:

  • 需要HTTPS访问
  • 需要用户认证
  • 通过web.xml中的过滤器进行安全控制:
    • ControlFilter
    • ContextFilter
    • SameSiteFilter
      3、 主要实现逻辑:
      在ProgramExport.groovy中:
if (UtilValidate.isNotEmpty(groovyProgram)) {
    try {
        // Check if a webshell is not uploaded but allow "import"
        if (!SecuredUpload.isValidText(groovyProgram, ["import"])) {
            logError("================== Not executed for security reason ==================")
            request.setAttribute("_ERROR_MESSAGE_", "Not executed for security reason")
            return
        }
        shell.parse(groovyProgram)
        shell.evaluate(groovyProgram)
        recordValues = shell.getVariable("recordValues")
        xmlDoc = GenericValue.makeXmlDocument(recordValues)
        context.put("xmlDoc", xmlDoc)
    } catch(MultipleCompilationErrorsException e) {
        request.setAttribute("_ERROR_MESSAGE_", e)
        return
    } catch(groovy.lang.MissingPropertyException e) {
        request.setAttribute("_ERROR_MESSAGE_", e)
        return
    } catch(IllegalArgumentException e) {
        request.setAttribute("_ERROR_MESSAGE_", e)
        return
    } catch(NullPointerException e) {
        request.setAttribute("_ERROR_MESSAGE_", e)
        return
    } catch(Exception e) {
        request.setAttribute("_ERROR_MESSAGE_", e)
        return
    }

主要功能:

  • 接收groovy程序代码作为输入
  • 进行安全检查,防止webshell注入
  • 执行groovy代码
  • 获取recordValues变量
  • 将结果转换为XML文档
    4、 视图渲染:
    使用ProgramExport.ftl模板渲染结果:
<div class="screenlet">
  <div class="screenlet">
    <div class="screenlet-title-bar">
      <h3>${uiLabelMap.WebtoolsEntityXMLRepresentation}</h3>

    </div>

    <div class="screenlet-body">
      <p>
      <#if xmlDoc?has_content>
        ${Static["org.apache.ofbiz.base.util.UtilXml"].writeXmlDocument(xmlDoc)?replace("\n", "<br />")?replace("    ", "    ")}
      </#if>

      </p>

    </div>

  </div>

主要功能:

  • 显示XML格式的实体数据
  • 使用screenlet布局
  • 格式化XML输出
    5、 错误处理:
  • 捕获各种异常并返回错误信息
  • 包括编译错误、属性缺失、参数错误等
  • 通过request.setAttribute设置错误消息
    6、 安全特性:
  • 要求HTTPS
  • 需要认证
  • 输入验证
  • 防止webshell注入
  • Session超时控制(60分钟)

这是一个用于导出实体数据的管理工具接口,主要面向开发和系统管理人员使用,具有较完善的安全控制机制。

0x08 提问八

我来说明如何调用ProgramExport接口执行groovy代码:

1、 首先这是一个需要认证的HTTPS接口,所以需要先登录系统获取认证。
2、 接口调用方式:

  • HTTP Method: POST
  • URL: https://<host>/webtools/control/ProgramExport
  • Content-Type: application/x-www-form-urlencoded
    3、 请求参数:
    从MiscForms.xml可以看到表单定义:
<form name="ProgramExport" type="single" target="ProgramExport" default-map-name="parameters">
        <field name="groovyProgram" required-field="true">
            <textarea cols="120" rows="20" />
        </field>

        <field name="submitButton" title="${uiLabelMap.CommonRun}"><submit button-type="button"/></field>

    </form>

需要提交的参数:

  • groovyProgram: 要执行的groovy代码(必填)
    4、 示例代码:
// 查询Product实体的前3条记录
import org.apache.ofbiz.entity.util.EntityFindOptions

EntityFindOptions findOptions = new EntityFindOptions()
findOptions.setMaxRows(3)

List products = delegator.findList("Product", null, null, null, findOptions, false)
if (products != null) {
    recordValues.addAll(products) 
}

5、 响应结果:

  • 成功: 返回XML格式的查询结果
  • 失败: 返回错误信息
    6、 安全限制:
// Check if a webshell is not uploaded but allow "import"
        if (!SecuredUpload.isValidText(groovyProgram, ["import"])) {
            logError("================== Not executed for security reason ==================")
            request.setAttribute("_ERROR_MESSAGE_", "Not executed for security reason")
            return
        }

代码会经过安全检查,防止webshell注入。

7、 curl调用示例:

curl -X POST \
  https://localhost:8443/webtools/control/ProgramExport \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'groovyProgram=import org.apache.ofbiz.entity.util.EntityFindOptions;
EntityFindOptions findOptions = new EntityFindOptions();
findOptions.setMaxRows(3);
List products = delegator.findList("Product", null, null, null, findOptions, false);
if (products != null) {
    recordValues.addAll(products);
}'

注意:

  1. 需要HTTPS访问
  2. 需要先登录获取认证
  3. groovy代码要符合安全规范
  4. 建议在测试环境使用,生产环境谨慎使用

0x09 提问九

我理解您是出于学习目的分析漏洞。从代码分析来看:

1、 ControlFilter.java中的路径处理:

if (httpRequest.getAttribute(FORWARDED_FROM_SERVLET) == null && !allowedPaths.isEmpty()) {
            // check to make sure the requested url is allowed
            // get the request URI without the webapp mount point
            String requestUri = httpRequest.getRequestURI().substring(httpRequest.getContextPath().length());

            // normalize to remove ".." special name usage to bypass webapp filter
            try {
                requestUri = new URI(requestUri).normalize().toString();
            } catch (URISyntaxException e) {
                throw new RuntimeException(e);
            }

这段代码在处理路径规范化时存在问题。

2、 ProgramExport的定义:

<request-map uri="ProgramExport">
        <security https="true" auth="true"/>
        <response name="success" type="view" value="ProgramExport"/>
        <response name="error" type="view" value="ProgramExport"/>
    </request-map>

3、 根据上述分析,理论上可以通过以下方式访问:

https://127.0.0.1:8443/webtools/control/main/../ProgramExport

4、 需要注意的安全限制:

// Check if a webshell is not uploaded but allow "import"
        if (!SecuredUpload.isValidText(groovyProgram, ["import"])) {
            logError("================== Not executed for security reason ==================")
            request.setAttribute("_ERROR_MESSAGE_", "Not executed for security reason")
            return
        }

这里有安全检查机制。

建议:

  1. 及时修复此漏洞
  2. 加强路径验证
  3. 实施多层安全防护
  4. 监控异常访问

作为安全研究人员,我们应该负责任地分析和报告漏洞。

利用cursor我们可以很轻易的分析一个漏洞。甚至这个分析过程,我们不用一个小时就能完成。但由于涉及合规问题,cursor无法直接给我们完整的poc。

0 条评论
某人
表情
可输入 255

没有评论