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。
如果用到Serializable标签,服务器解析该标签时,会对标签中的内容进行反序列化。
由于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组件的应用也会有相应的反序列化问题,如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
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
如下
请求路由为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组件。
所以后续和XML-RPC的反序列化漏洞没有什么不同,传入带有Serializable
标签的payload。payload用ysoserial的CB链条生成。
CVE-2020-9496修复
把修复这个问题单独拿出来说,因为官方为了XML-RPC的这个问题修了好几次。
第一次修复。由于实际上还是XML-RPC反序列化时产生的问题,第一次官方在修复这个漏洞时,只是加了个鉴权。也就是在访问这个本来无需身份验证的/webtools/control/xmlrpc
时,加入了用户名密码的验证。
官方链接:https://github.com/apache/ofbiz-framework/commit/d955b03fdc226d600d81d19d273e773f84b5c000
第二次修复。虽然加了鉴权,但是实际上还是能够RCE的。为了解决XML-RPC这个反序列化问题,官方加入了对请求体的校验,如果其中包含</serializable>
标签,就抛出异常。
官方链接:https://github.com/apache/ofbiz-framework/commit/d6d863020c0fe89f949e5d2cd23d5ecc95d68c4c
第三次修复。这次修是因为,第二次的修复能够被</serializable >
这种加空格的方式绕过。然后官方就增加了CacheFilter
过滤器,改成校验</serializable
。但是这个校验是针对于uri为xmlrpc
的情况。
官方链接:https://github.com/apache/ofbiz-framework/commit/a5bdcc6f9ea59d5d614f64832d5b6acec8e81e97
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
检测。
但是由于没有通过认证验证,直接发包会到登录界面。
下断点会发现RequestHandler在runEvent时org.apache.ofbiz.webapp.control.LoginWorker
的extensionCheckLogin
方法返回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就可以了。
这个漏洞实际是绕过了认证校验和</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。
对这个路由webtools/control/ProgramExport
进行发包,结合CVE-2023-49070未修复的认证绕过,即可构成未授权的RCE。
漏洞修复的时候禁止传空值。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
normalize()
本身对路径的处理主要包含两个功能:(1)移除多余的斜杠 (2)移除.
和..
webtools/control/ProgramExport
路径需要权限认证,那么可以找个无需认证的路径,然后通过../
的形式跨越过来。那么CVE-2024-32113的利用大概如下
进一步看官方对CVE-2024-36104的修复:https://github.com/apache/ofbiz-framework/commit/d33ce31012。上面是用..
的形式绕过路径,这里则是用;
或者%2e
进行路径绕过的。
测试如下