Apache OFBiz中的XML-RPC问题

Apache OFBiz(Open For Business)是一个开源企业资源规划(ERP)系统,用于统一管理企业的各种资源、流程和数据。源码下载地址:https://archive.apache.org/dist/ofbiz/

组件出现过的漏洞和对应的修复,都可以在官方security公告下找到:https://ofbiz.apache.org/security.html

组件出现过不少漏洞。其中一种就是XML-RPC反序列化漏洞。最开始Apache OFBiz出现XML-RPC反序列化漏洞CVE-2020-9496,由于这是XML-RPC第三方组件自身的问题,而不是Apache OFBiz本身的问题。官方修复时(1)对XML-RPC路径访问进行身份认证,(2)禁止请求体包含<serializable>标签。

由于对XML-RPC修复时加入的路径判断存在逻辑问题,补丁被绕过,也就是CVE-2023-49070。后来官方干脆把XML-RPC这个功能干掉了。

但是认证的问题没有修复,所以攻击者找到了后台可以执行Groovy脚本的地方,配合这个认证绕过又搞出了未授权RCE—CVE-2023-51467。然后官方对认证问题进行了修复。后来有攻击者发现可以用无需认证的路径,配合..;等符号执行到需要认证的路径,依旧能造成CVE-2023-51467的效果,即CVE-2024-32113和CVE-2024-36104。这部分内容因为不是XML-RPC的重点,但是既然存在逻辑连贯性,文章里就简单写一下。

XML-RPC

首先要了解什么是RPC。远程过程调用(RPC,Remote Procedure Call)是分布式计算中的一种技术,它允许一个程序调用另一个程序(在不同的机器上)的函数或方法,像调用本地函数一样调用远程函数。

早期计算机的资源是分散的,开发者想要处理不同计算机之间的远程通信,需要通过底层的Socket,处理相关的网络协议、数据序列化等问题。后来提出RPC的思想,RPC框架负责这些底层的问题,开发者只需要调用方法。RPC一个很重要的概念是代理(Stub)的应用。客户端看似调用的是本地的方法,实际调用的是代理的函数,代理负责将接收到的方法名和参数,序列化成一个标准格式(XML或者二进制等),然后通过网络发送给服务器。

XML-RPC(XML Remote Procedure Call)就是采用XML格式来对信息进行编码。常见格式如下。请求格式包含方法名(methodName)和参数(param)。
请求(左) 响应(右)

参数中能用到的数据类型参考官网:https://ws.apache.org/xmlrpc/types.html。常见的基础类型包括:Integer、Boolean、String等标签,如果设置了**enabledForExtensions**属性,则支持Byte、java.io.Serializable等标签。

在xmlrpc组件的源码中也可以看到上述数据类型的解析逻辑。首先判断了**enabledForExtensions**属性是否为true。
xmlrpc getParse源码

如果用到Serializable标签,服务器解析该标签时,会对标签中的内容进行反序列化。
xml-rpc解析时会用原生反序列化

由于XML-RPC本身就具备反序列化功能,所以这个组件出现过两个反序列化漏洞:CVE-2016-5003、CVE-2019-17570。参见:https://github.com/orangecertcc/security-research/security/advisories/GHSA-x2r6-4m45-m4jp

需要注意的是,XML RPC在2010年之后就不再更新了,所以它出现的反序列化漏洞也没有被修复
xml-rpc 更新时间

所以一些用到XML-RPC组件的应用也会有相应的反序列化问题,如Apache OFBiz(CVE-2020-9496)、Zoho Password Manager Pro(CVE-2022-35405)等。由于XML-RPC在各个应用中的反序列化问题都是类似的,只不过需要找到应用在什么地方对XML-RPC的内容进行解析的。从Apache OFBiz中就可以把这个问题学明白。

CVE-2020-9496

漏洞发现者是国外知名黑客@pwntester (Alvaro Muñoz),文章链接:https://securitylab.github.com/advisories/GHSL-2020-069-apache_ofbiz/

漏洞描述写到,Apache OFBiz的XML-RPC有个未经身份验证的公开端点/webtools/control/xmlrpc

漏洞涉及的webtools,位于framework目录,查看该目录的路由信息framework/webtools/webapp/webtools/WEB-INF/web.xml

framework/webtools/webapp/webtools/WEB-INF/web.xml

ControlServlet中对请求的处理,实际调用了org/apache/ofbiz/webapp/control/RequestHandler.java。大致的处理逻辑是读取controller配置文件,根据请求的路由找到对应的<request-map>标签中的内容,根据其中的<event>标签对应的type类型,找到相应的Handler处理器。

// RequestHandler
ConfigXMLReader.ControllerConfig controllerConfig = this.getControllerConfig();
Map<String, ConfigXMLReader.RequestMap> requestMapMap = controllerConfig.getRequestMapMap();
String defaultRequestUri = RequestHandler.getRequestUri(request.getPathInfo());
if (defaultRequestUri != null) {
    requestMap = requestMapMap.get(defaultRequestUri);
}

for (ConfigXMLReader.Event event: controllerConfig.getPreprocessorEventList().values()) {
    try {
        String returnString = this.runEvent(request, response, event, null, "preprocessor");
    }
}

public String runEvent(HttpServletRequest request, HttpServletResponse response,ConfigXMLReader.Event event, ConfigXMLReader.RequestMap requestMap, String trigger) throws EventHandlerException {
    EventHandler eventHandler = eventFactory.getEventHandler(event.type);
    String eventReturn = eventHandler.invoke(event, requestMap, request, response);
}

配置文件controller.xml如下
controller.xml

请求路由为xmlrpc,即找到<request-map url="xmlrpc">。对应的event处理器就是XmlRpcEventHandler。然后Handler执行自身的execute方法,对请求的xml格式进行处理。

// XmlRpcEventHandler
public void execute(XmlRpcStreamRequestConfig pConfig,ServerStreamConnection pConnection) throws XmlRpcException {
    Object result = null;
    boolean foundError = false;

    try (InputStream istream = getInputStream(pConfig, pConnection)) {
        XmlRpcRequest request = getRequest(pConfig, istream);
        result = execute(request);
    }
}

XmlRpcEventHandler在获取数据流后执行了getRequest()操作,这步就是在对xml进行解析。而实际的解析xr.parse()则是调用的XML-RPC组件。
XmlRpcEventHandler#XmlRpcRequest

所以后续和XML-RPC的反序列化漏洞没有什么不同,传入带有Serializable标签的payload。payload用ysoserial的CB链条生成。
CVE-2020-9496漏洞复现

CVE-2020-9496修复

把修复这个问题单独拿出来说,因为官方为了XML-RPC的这个问题修了好几次。

第一次修复。由于实际上还是XML-RPC反序列化时产生的问题,第一次官方在修复这个漏洞时,只是加了个鉴权。也就是在访问这个本来无需身份验证的/webtools/control/xmlrpc时,加入了用户名密码的验证。

官方链接:https://github.com/apache/ofbiz-framework/commit/d955b03fdc226d600d81d19d273e773f84b5c000

CVE-2020-9496 修复

第二次修复。虽然加了鉴权,但是实际上还是能够RCE的。为了解决XML-RPC这个反序列化问题,官方加入了对请求体的校验,如果其中包含</serializable>标签,就抛出异常。

官方链接:https://github.com/apache/ofbiz-framework/commit/d6d863020c0fe89f949e5d2cd23d5ecc95d68c4c

CVE-2020-9496 二次修复

第三次修复。这次修是因为,第二次的修复能够被</serializable >这种加空格的方式绕过。然后官方就增加了CacheFilter过滤器,改成校验</serializable。但是这个校验是针对于uri为xmlrpc的情况。

官方链接:https://github.com/apache/ofbiz-framework/commit/a5bdcc6f9ea59d5d614f64832d5b6acec8e81e97

CVE-2020-9496 第三次修复

CVE-2023-49070

CVE-2023-49070则是绕过了上述的补丁。一是绕过了</serializable的检测。二是绕过了认证的校验。

在这个版本的Apache OFBiz下,如果还是发送CVE-2020-9496的POC会发现先经过CacheFilter,然后被</serializable匹配到直接return。那么如何绕过</serializable的检测呢?从标签上入手没有什么更改的空间,但是如果路径无法匹配,走不到if中,那就不会去校验</serializable

这里用到了Matrix Parameters,中文叫矩阵参数。它是URL的一部分,用于在URL路径中传递复杂或结构化的数据。与传统的查询参数(Query Parameters)不同,矩阵参数使用分号;而不是问号?和与号&来分隔参数。

Apache OFBiz底层的服务器用的Tomcat,而Tomcat自身是支持Matrix Parameters的,可以在路径中用;的形式嵌入参数。当在路径后加入分号后就不满足equals的if判断了。直接进入到下一个Filter。从而绕过了后续的</serializable检测。
加入Matrix Parameters后路径不相等

但是由于没有通过认证验证,直接发包会到登录界面。
Matrix Parameters绕过路径判断

下断点会发现RequestHandler在runEvent时org.apache.ofbiz.webapp.control.LoginWorkerextensionCheckLogin方法返回error,没有进入后续的xmlrpc。

跟进该方法,发现实际调用的是同类的checkLogin,核心判断如下。只要传入的(1) username为null (2) password为null (3) login返回error满足三个中的一个条件,都会进入if判断,从而返回error。username和password是通过参数传入的,所以可以让它们不为null,那么只需要能让login方法不返回error即可绕过。另外,需要注意,值为null和值为空的区别。值为null是没有传入username,值为空("")是传入username=

if (username == null || (password == null && token == null) 
 || "error".equals(login(request, response))) {
    ...
    return "error";
}

login部分代码如下,发包的时候传入USERNAME=&PASSWORD=此时unpwErrMsgList包含两个值

0 = "用户名是空的,请重新输入"
1 = "密码是空的,请重新输入。"

然后就会进入到如下的if中

boolean requirePasswordChange = "Y".equals(request.getParameter("requirePasswordChange"));
if (!unpwErrMsgList.isEmpty()) {
    request.setAttribute("_ERROR_MESSAGE_LIST_", unpwErrMsgList);
    return  requirePasswordChange ? "requirePasswordChange" : "error";
}

根据前面的逻辑,只要这个login不返回error就认证通过。那将requirePasswordChange参数值设为Y就可以了。

CVE-2023-49070漏洞复现

这个漏洞实际是绕过了认证校验和</serializable校验。官方修复时,直接干掉了XML-RPC,不再使用这个组件,也不再支持相关功能。https://github.com/apache/ofbiz-framework/commit/c59336f604f503df5b2f7c424fd5e392d5923a27

CVE-2023-51467

CVE-2023-49070修复时只是不再使用XML-RPC,避免了反序列化的安全问题,但是并没有对认证绕过进行修复,所以可以找到其他可利用的点,来进行未授权的RCE。长亭的师傅找到了一个执行Groovy脚本的点,也就是CVE-2023-51467。

先用默认的账户名密码admin/ofbiz进入系统,找到能够执行Groovy脚本的地方,同样位于webtools。
Groovy脚本执行功能ProgramExport

对这个路由webtools/control/ProgramExport进行发包,结合CVE-2023-49070未修复的认证绕过,即可构成未授权的RCE。
CVE-2023-51467漏洞复现

漏洞修复的时候禁止传空值。https://github.com/apache/ofbiz-framework/commit/d8b097f6717a4004acf023dfe929e0e41ad63faa#diff-68decfd4946b8ef0adcc4c7f18b938aec4a07ff7ce64609a2691ba88a4688607

通过路径绕过

CVE-2024-32113、CVE-2024-36104这两个官方都标注的“Path Traversal”漏洞,但实际上利用时也是上面CVE-2023-51467的绕过形式。所以一起放在文章里讲了。

官方在修复CVE-2024-32113这个漏洞时对路径增加了normalize()处理:https://github.com/apache/ofbiz-framework/commit/b3b87d98dd

CVE-2024-32113漏洞修复

normalize()本身对路径的处理主要包含两个功能:(1)移除多余的斜杠 (2)移除...

webtools/control/ProgramExport路径需要权限认证,那么可以找个无需认证的路径,然后通过../的形式跨越过来。那么CVE-2024-32113的利用大概如下

CVE-2024-32113复现

进一步看官方对CVE-2024-36104的修复:https://github.com/apache/ofbiz-framework/commit/d33ce31012。上面是用..的形式绕过路径,这里则是用;或者%2e进行路径绕过的。
CVE-2024-36104修复

测试如下
CVE-2024-36104复现

  • 10
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值