介绍
CVE-2024-25065 是一个存在于 Apache OFBiz 在版本 18.12.12 之前的漏洞。这是一种路径遍历漏洞,允许通过 hasBasePermission()方法中的 contextPath 变量进行身份验证绕过。
Apache OFBiz 是什么?
Apache OFBiz(Open For Business)是一个开源的企业资源规划(ERP)和电子商务系统,提供了一套全面的商业应用程序,用于自动化和整合各种业务流程。OFBiz 使用 Java 和 XML 构建,可高度定制和可扩展,适用于中小型企业和具有复杂流程的企业。其模块化设计包括会计、库存管理、制造、订单管理和采购等 ERP 模块,以及电子商务、客户关系管理(CRM)和人力资源功能。OFBiz 支持强大的数据模型和面向服务的体系结构(SOA),实现业务逻辑和流程的无缝管理。作为 Apache 软件基金会的项目,OFBiz 受益于众多开发人员和广泛的文档,确保持续的支持和合作。带有 Apache Tomcat、Apache Derby、Freemarker 和 Groovy 等关键技术,OFBiz 为希望简化运营而不需要昂贵许可费的企业提供了一个集成解决方案。
对比修复补丁
LoginWorker.java
修补了此漏洞的提交位于:framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/LoginWorker.java:
在这里添加了如下代码:
图 1:补丁添加的代码
这段代码的目的是确保 contextPath 变量是一个有效的、标准化的 URI 字符串。标准化 URI 包括移除冗余的段落(例如 . 和 …)、解析相对路径以及将 URI 转换为标准格式的过程。
显然,正如 CVE 描述中所述,contextPath 中存在路径遍历漏洞。
准备测试环境
由于 18.12.12 版本之前的版本存在漏洞,因此较旧的版本在编译构建过程中可能会遇到问题。相比之下,18.12.05 版本的问题较少,你可以在此下载该版本。
首先,我们需要 OpenJDK-8:
下载 openJDK8
wget https://download.java.net/openjdk/jdk8u41/ri/openjdk-8u41-b04-linux-x64-14_jan_2020.tar.gz
解压
tar -xvf openjdk-8u41-b04-linux-x64-14_jan_2020.tar.gz
将其移动到JVM文件夹
sudo mv java-se-8u41-ri /usr/lib/jvm/openjdk-8
将其添加到PATH中
export JAVA_HOME=/usr/lib/jvm/openjdk-8
export PATH=
J
A
V
A
H
O
M
E
/
b
i
n
:
JAVA_HOME/bin:
JAVAHOME/bin:PATH
将update-alternatives设置为OpenJDK8
sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/openjdk-8/bin/java 1
sudo update-alternatives --install /usr/bin/javac javac /usr/lib/jvm/openjdk-8/bin/javac 1
然后选择OpenJDK8版本
sudo update-alternatives --config java
sudo update-alternatives --config javac
验证安装
java -version
#现在,是时候构建我们的OFBiz版本了:
#使用以下命令
./gradlew cleanAll loadAll
安装过程中的错误处理
在构建过程中可能会遇到问题,比如缺少 gradle-wrapper.jar 文件。
您可以使用以下命令来解决这个问题:
gradle wrapper
或者手动下载:
wget https://services.gradle.org/distributions/gradle-6.8.3-all.zip
另一个问题将与 CA 证书有关:它可以通过更新或重新安装来轻松修复。
sudo apt install --reinstall ca-certificates
运行 OFBiz
我们将在调试模式下运行 OFBiz:
./gradlew ofbiz --debug-jvm
你可能面临的另一个问题是在我们以调试模式运行时与_JAVA_OPTIONS相关:
所以,我们将取消设置它
unset _JAVA_OPTIONS
或者
只需将其设置为默认设置
export _JAVA_OPTIONS=“-Dawt.useSystemAAFontSettings=on -Dswing.aatext=true”
图 2:调式模式启动 ofbiz
环境已准备好,调试端口在 5005 上。
分析
从补丁差异中我们知道问题存在于 LoginWorker.java,具体来说是 hasBasePermission()方法。让我们来看一下这个方法的代码并对其进行分析:
静态分析
图 3:hasBasePermission()方法
让我们来详细解析这段方法代码:
hasBasePermission()方法
public static boolean hasBasePermission(GenericValue userLogin, HttpServletRequest request)
这个方法接收两个参数:第一个是 GenericValue 类型的 userLogin,它承载了用户的登录信息;第二个参数是 HttpServletRequest 类型的 request,它代表了一个HTTP请求,我们可以从中获取请求参数、属性等数据。
Security security = (Security) request.getAttribute(“security”);
在这个方法中,我们从 HttpServletRequest 对象中获取 Security 对象,该对象将用于后续的权限检查。
if (security != null) {
它检查安全对象是否不为空。如果不为空,它将执行以下操作:
ServletContext context = request.getServletContext();
String serverId = (String) context.getAttribute(“_serverId”);
String contextPath = request.getContextPath();
它检索serverId和contextPath。服务器ID唯一标识服务器实例,而contextPath对于识别网络应用配置以及确定要操作的端点是必需的。
if (UtilValidate.isEmpty(contextPath)) {
contextPath = “/”;
}
如果contextPath为空,则默认为根路径(“/”)。
ComponentConfig.WebappInfo info = ComponentConfig.getWebAppInfo(serverId, contextPath);
在这里,它将使用 serverId 和 contextPath 获取 Web 应用程序配置信息,以获取权限检查所需的配置详细信息。
if (info != null) {
return hasApplicationPermission(info, security, userLogin);
} else {
if (Debug.infoOn()) {
Debug.logInfo("No webapp configuration found for : " + serverId + " / " + contextPath, module);
}
}
如果 info 不为空,则调用 hasApplicationPermission 方法检查用户是否具有应用程序所需的权限。我们将在稍后讨论 hasApplicationPermission()方法。
} else {
if (Debug.warningOn()) {
Debug.logWarning("Received a null Security object from HttpServletRequest", module);
}
}
否则,这表示配置错误,表明从 HttpServletRequest 接收到了一个空的 Security 对象。
hasApplicationPermission() 方法
图 4: hasApplicationPermission() 方法
public static boolean hasApplicationPermission(ComponentConfig.WebappInfo info, Security security, GenericValue userLogin)
此方法接受以下参数:ComponentConfig.WebappInfo info,其中包含 Web 应用程序的配置信息;Security security,提供检查权限的安全管理器方法;以及 GenericValue userLogin,其中包含用户登录信息。
String accessPermission = info.getAccessPermission();
它检索 WebappInfo 对象中定义的访问权限。info.getAccessPermission()获取指定 Web 应用程序所需访问级别的权限字符串。
if (!accessPermission.isEmpty()) {
return security.hasPermission(accessPermission, userLogin);
如果 accessPermission 不为空,则通过调用 security.hasPermission(accessPermission, userLogin)来检查用户是否具有此特定权限。如果用户有权限,则返回 true;否则返回 false。
else {
String[] var4 = info.getBasePermission();
int var5 = var4.length;
for(int var6 = 0; var6 < var5; ++var6) {
String permission = var4[var6];
if (!“NONE”.equals(permission) && !security.hasEntityPermission(permission, “_VIEW”, userLogin)) {
return false;
}
}
如果访问权限为空,则 info.getBasePermission()返回基本权限数组,int var5 = var4.length 获取基本权限数组的长度。它遍历基本权限数组中的每个权限。最后,它检查每个基本权限以确定用户是否具有必要的权限。如果权限不是“NONE”,并且用户缺少给定权限和操作“_VIEW”的特定实体权限,则返回 false。
动态分析
让我们继续动态地进行分析:
图 5:从Run/Debug配置中选择远程JVM调试。
图 6:通过输入OFBiz服务器IP和调试端口(即5005)来配置它。
图 7:运行调试器,并在 hasBasePermission() 上设置断点。
通过访问 https://localhost:8443/webtools/control/checkLogin,我们可以看到登录页面,我们可以使用 admin&ofbiz 作为用户名和密码:
图 8:OFBiz 的登录页面
当我们点击登录时,我们将触发调试器中的断点
图 9:当我们执行下一步时,我们看到它从请求中获得了安全属性
图 10:当它检查是否为空时,我们注意到它不为空,因此它通过了检查。
图 11:执行下一步时,它会做以下事情
它获取了请求的上下文信息:通过 request.getServletContext() 方法。
获取了服务器的唯一标识:使用 context.getAttribute(“_serverId”)获取 String 类型的 serverId。
对我们来说最关键的是 contextPath:通过 request.getContextPath() 获取 String 类型的 contextPath,它存储的值是 “/webtools”。
接下来,它会检查根路径 “/”,由于我们已经定义了一个路径,它不会重复处理。然后,它将转向获取 Web 应用信息的步骤:通过 ComponentConfig.getWebAppInfo(serverId, contextPath)方法获取 ComponentConfig.WebappInfo 类型的对象 info。在这里,它检索到相关信息:
图 12:info对象信息
图 13:由于信息不为空,它将执行
返回 hasApplicationPermission(info, security, userLogin);
当我们下一步时,我们会看到以下内容:
图 14:转到 doMainLogin()方法
具体在以下行:
else if (userLogin != null && hasBasePermission(userLogin, request))
并完成执行正常的登录请求。登录后,我们看到:
图 15:登录后的界面
检查具有有限权限的用户
现在,我们将使用 bizadmin 和密码 ofbiz,尝试访问 https://localhost:8443/partymgr/control/FindSecurityGroup:
图 16:没有权限查看
我们可以清楚地看到我们没有权限查看它。让我们调试一下:
图 17:调试
在进行与上述相同的步骤后,在调试我们的登录过程时,当它进入 hasApplicationPermission()方法时,它开始通过 String accessPermission = info.getAccessPermission();来检索权限。
图 18:accessPermission 为空
我们看到 accessPermission 为空,所以它会遍历权限数组。当我们暂停一会儿时:
图 19: hasEntityPermission()方法
它到达了 hasEntityPermission()方法,该方法检查我们拥有的权限和访问 adminPermission 所需的权限
它会持续迭代数组:由于我们的权限不匹配,它拒绝了我们的请求,不显示页面。
Exploitation
现在,由于 contextPath 上没有规范化,让我们在 BurpSuite 中拦截请求,然后当我们提供遍历路径时,通过调试器检查它
图 20:bp 拦截请求
在这里我们将登录到/partymgr/control/login,但是我们添加了我们的路径遍历以指示/webtools/control/login:
图 21:路径遍历以指示/webtools/control/login
当它再次到达我们的断点时,我们清楚地看到了请求的 contextPath。
图 22:请求的contextPath
我们通过了这个检查,因为它有一个有效的 URI。
图 23:contextPath 路径信息
当我们到达 contextPath 时,我们看到它接受了我们的遍历路径。
图 24:当它尝试获取 appInfo 时,由于 contextPath,它返回 null
图 25:返回 true
然后,它返回 true 并允许我们登录到 webtools:
图 26:登录到 webtools
图 27:浏览器上显示
结论
这还不算完,访问其他端点时你可能还会遇到错误。但是,即使你以低权限用户身份在利用路径遍历漏洞的过程中对这些端点进行操作并收到了错误信息,这些操作仍然会被执行。利用这一漏洞,攻击者可能未授权地访问应用程序中的多种功能和数据。为了解决这个问题,请升级到 Apache OFBiz 18.12.12 版本或更高版本,该版本已经通过标准化 contextPath 变量,确保其为有效的 URI,从而修补了这一漏洞。