Vulnerability Research

Assessing CVE-2024-25065: Apache OFBiz Security bypass leads to Unauthorized Access/Actions

By SecureLayer7 Lab

11 min read

Introduction

CVE-2024-25065 is a vulnerability that exists in Apache OFBiz before version 18.12.12. It is a path traversal vulnerability that allows authentication bypass through the contextPath variable within the hasBasePermission() method.

What is Apache OFBiz ?

Apache OFBiz (Open For Business) is an open-source enterprise resource planning (ERP) and e-commerce system that offers a comprehensive suite of business applications to automate and integrate various business processes. Built using Java and XML, OFBiz is highly customizable and scalable, making it suitable for small to medium-sized enterprises (SMEs) and businesses with complex processes. Its modular design includes ERP modules for accounting, inventory management, manufacturing, order management, and procurement, as well as e-commerce, customer relationship management (CRM), and human resources features. OFBiz supports a robust data model and service-oriented architecture (SOA), allowing for seamless management of business logic and processes. As an Apache Software Foundation project, OFBiz benefits from a large community of developers and extensive documentation, ensuring ongoing support and collaboration. With key technologies like Apache Tomcat, Apache Derby, Freemarker, and Groovy, OFBiz provides an integrated solution for businesses looking to streamline their operations without the need for expensive licensing fees.

Patch Diffing

LoginWorker.java

The commit that patched this vulnerability is in: framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/LoginWorker.java:

The following code block was added:from here, is on

Code added in the LoginWorker.java patch to normalize and validate the contextPath URI

This code aims to ensure that the contextPath variable is a valid, normalized URI string. Normalizing a URI involves processes like removing redundant segments (e.g., . and ..), resolving relative paths, and converting the URI to a standard format.

Clearly, it was a path traversal vulnerability within contextPath as described in the CVE description.

This code aims to ensure that the contextPath variable is a valid, normalized URI string. Normalizing a URI involves processes like removing redundant segments (e.g., . and ..), resolving relative paths, and converting the URI to a standard format.

Clearly, it was a path traversal vulnerability within contextPath as described in the CVE description.

Testing Lab

Since versions before 18.12.12 are vulnerable, most of the previous versions may have issues while building. The one with the fewest issues is 18.12.05, which you can download from here.

  • First we would need OpenJDK-8:
# Download openJDK8

wget https://download.java.net/openjdk/jdk8u41/ri/openjdk-8u41-b04-linux-x64-14_jan_2020.tar.gz

# Extract it

tar -xvf openjdk-8u41-b04-linux-x64-14_jan_2020.tar.gz

# Move it to the JVM folder

sudo mv java-se-8u41-ri /usr/lib/jvm/openjdk-8

# Add it to the PATH

export JAVA_HOME=/usr/lib/jvm/openjdk-8

export PATH=$JAVA_HOME/bin:$PATH

# Set up the update-alternatives system to 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

# Then choose the OpenJDK8 version

sudo update-alternatives --config java

sudo update-alternatives --config javac

# Verify the installation

java -version

Now, It's time to build our OFBiz version:

#Using the following command

./gradlew cleanAll loadAll

Fix issues

You may face issues during the build process, such as the absence of the gradle-wrapper.jar file.

  • You can solve it using the following command:

gradle wrapper

Or download it manually: 

# Download manually wget https://services.gradle.org/distributions/gradle-6.8.3-all.zip

  • Another issue would be with the ca-certificate:

It can easily be fixed by updating it or re-installation.

sudo apt install –reinstall ca-certificates

Run OFBiz

  • We will run OFBiz in debugging mode:
./gradlew ofbiz --debug-jvm

Another Issue you might face is with the _JAVA_OPTIONS, when we run it in debugging mode:

# So, We will just unset it

unset _JAVA_OPTIONS

or

# Just set it to the default settings

export _JAVA_OPTIONS="-Dawt.useSystemAAFontSettings=on -Dswing.aatext=true"
Terminal unsetting _JAVA_OPTIONS so OFBiz can be launched in debugging mode

It’s ready for our analysis & the debugging port is on 5005.

The Analysis

From the patch diffing, we know that the issue existed in LoginWorker.java, specifically under the hasBasePermission() method. Let’s take a look at this method’s code and analyze it:

Static Analysis

Source code of LoginWorker.java opened for static analysis of the hasBasePermission() method

Let’s break down the method code:

public static boolean hasBasePermission(GenericValue userLogin, HttpServletRequest request)

This method takes two parameters: GenericValue userLogin, which represents the user’s login information, and HttpServletRequest request, which is the HTTP request object, providing access to request parameters, attributes, and other data.

Security security = (Security) request.getAttribute(“security”);

The method retrieves the Security object from the HttpServletRequest, which will be used for checking permissions.

if (security != null) {

It checks if the Security object is not null. If it’s not null, it will do the following:

ServletContext context = request.getServletContext();

String serverId = (String) context.getAttribute("_serverId");

String contextPath = request.getContextPath();

It retrieves serverId and contextPath. The server ID uniquely identifies the server instance, and the contextPath is necessary for identifying the web application configuration and which endpoint to work on.

if (UtilValidate.isEmpty(contextPath)) {

    contextPath = "/";

}

If the contextPath is empty, it defaults to the root path ("/").

ComponentConfig.WebappInfo info = ComponentConfig.getWebAppInfo(serverId, contextPath);

Here, it will fetch the web application configuration info using the serverId and contextPath for configuration details needed for permission checks.

if (info != null) {

    return hasApplicationPermission(info, security, userLogin);

} else {

    if (Debug.infoOn()) {

        Debug.logInfo("No webapp configuration found for : " + serverId + " / " + contextPath, module);

    }

}

If info is not null, it calls hasApplicationPermission to check if the user has the required permissions for the application. We will discuss the hasApplicationPermission() method later.

} else {

    if (Debug.warningOn()) {

        Debug.logWarning("Received a null Security object from HttpServletRequest", module);

    }

}

Otherwise, it indicates a configuration error, showing that a null Security object was received from HttpServletRequest.

hasApplicationPermission() Method

Source code of the hasBasePermission() method in LoginWorker.java being analyzed

public static boolean hasApplicationPermission(ComponentConfig.WebappInfo info, Security security, GenericValue userLogin)

This method takes the following parameters: ComponentConfig.WebappInfo info, which holds configuration information for the web application; Security security, the security manager that provides methods to check permissions; and GenericValue userLogin, which holds the user’s login information.

String accessPermission = info.getAccessPermission();

It retrieves the access permission defined in the WebappInfo object. info.getAccessPermission() fetches the permission string that specifies the required access level for the web application.

if (!accessPermission.isEmpty()) {

    return security.hasPermission(accessPermission, userLogin);

}

If accessPermission is not empty, it checks if the user has this specific permission by calling security.hasPermission(accessPermission, userLogin). If the user has the permission, it returns true; otherwise, it returns 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;

            }

        }

If the access permission is empty, info.getBasePermission() returns an array of base permissions, and int var5 = var4.length gets the length of the base permissions array. It iterates over each permission in the base permissions array. Finally, it checks each base permission to determine if the user has the necessary permissions. If the permission is not “NONE” and the user lacks the specific entity permission for the given permission and the action “_VIEW”, it returns false.

Dynamic Analysis

Let’s go ahead and perform the analysis dynamically:

Source code of the hasApplicationPermission() method in OFBiz

Choose Remote JVM Debug from the Run/Debug Configurations.

IntelliJ Run/Debug Configurations dialog selecting Remote JVM Debug

Configure it by entering the OFBiz server IP and debugging port, which is 5005.

Run the debugger and set a breakpoint on hasBasePermission().

Remote debug configuration with the OFBiz server IP and debugging port 5005 entered

By visiting https://localhost:8443/webtools/control/checkLogin, We can see the login page, We can use admin& ofbiz as username and password:

OFBiz webtools login page reached at /webtools/control/checkLogin

When we hit login, we will trigger the breakpoint in our debugger:

Debugger paused at the breakpoint in hasBasePermission() after submitting the login

When we step over to the next line, we see that it got the Security attribute from the request:

Debugger showing the Security attribute retrieved from the request

As it will check if it’s null, we notice that it’s not null, so it passes the check.

Debugger confirming the Security object is not null, so the check passes

When we step over to the next steps, it does the following:

Debugger stepping into the next steps after the null check
  • It got the context: ServletContext context = request.getServletContext();
  • The serverId: String serverId = (String) context.getAttribute(“_serverId”);
  • And the most important to us, which is contextPath: String contextPath = request.getContextPath(); It holds the value of /webtools.

After that, it will check the root path /, and since we already have a path, it will not go through it. It will move to ComponentConfig.WebappInfo info = ComponentConfig.getWebAppInfo(serverId, contextPath); Here it fetches the info:

Debugger showing the ServletContext and contextPath obtained from the request

We see the info after it’s obtained. We see contextRoot holds the webtools endpoint, and the location webapp/webtools, etc.

Debugger showing the WebappInfo with contextRoot pointing to the webtools endpoint

Since the info is not null, it will do:

return hasApplicationPermission(info, security, userLogin);

When we step over it, we will see the following:

Debugger about to call hasApplicationPermission() since the info is not null

It jumps into doMainLogin() method, specifically on the following line:

else if (userLogin != null && hasBasePermission(userLogin, request))

And completes performing the normal login request. After login, we see:

Debugger stepping into the doMainLogin() method during a normal login

Check with limited permissions user

Now, We will use the bizadmin  with the password ofbiz, and we will try to access https://localhost:8443/partymgr/control/FindSecurityGroup:

Attempt to access FindSecurityGroup as the limited bizadmin user

We can clearly see we don’t have permissions to view it. Let’s debug it:

Access-denied result confirming the limited user lacks permission to view the page

After doing the same steps as above, during debugging our login, when it enters the hasApplicationPermission() method, it starts to retrieve the permissions through String accessPermission = info.getAccessPermission();.

Debugger in hasApplicationPermission() retrieving the access permission string

We see that the accessPermission is empty, so it goes through the permissions array. When we step over/into for a while:

Debugger showing the access permission is empty, so it iterates the permissions array

It reaches the hasEntityPermission() method, which checks the permission we have and the required permission to access adminPermission:

Debugger inside hasEntityPermission() comparing the held and required permissions

It keeps iterating through the array:

Debugger iterating through the permissions array, none of which match

As our permissions don’t match, it denies our request and doesn’t show us the page.

Exploitation

Now, as there is no normalization on the contextPath, let’s intercept the request in BurpSuite and then check it through the debugger when we provide a traversal path:

Burp Suite intercepting the login request before adding the path traversal

Here we will log in to /partymgr/control/login, but we add our path traversal to indicate /webtools/control/login:

Modified request logging in via /partymgr with a path traversal to reach /webtools/control/login

As it reaches our breakpoint again, we see the requested contextPath clearly.

Debugger at the breakpoint showing the requested contextPath with the traversal

We pass this check as it has a valid URI.

Debugger confirming the traversal contextPath passes the URI validity check

When we reach the contextPath, we see that it accepts our traversal path.

Debugger showing the contextPath accepts the traversal path

When it attempts to get the appInfo, it returns null due to the contextPath.

Debugger showing getAppInfo returns null because of the traversal contextPath

Then, it returns true and lets us log in to webtools:

Debugger showing the method returns true, granting login to webtools
  • On the browser

Conclusion

That’s not all, as you may still encounter errors when accessing other endpoints. However, if you perform actions on those endpoints with any low-permissions user while abusing the path traversal, even if you receive an error, the action still gets executed. By exploiting this vulnerability, attackers can gain unauthorized access to various functionalities and data within the application. To mitigate this issue, update to Apache OFBiz version 18.12.12 or later, where the vulnerability has been patched by normalizing the contextPath variable to ensure it is a valid URI.

Looking to strengthen your security posture? SecureLayer7 helps organizations identify vulnerabilities, reduce risk, and defend against evolving cyber threats. Contact our experts to get started.

References

// SecureLayer7

How SecureLayer7 helps

SecureLayer7 tests web applications like Apache OFBiz for path traversal and authentication bypass flaws before attackers find them. Our assessments cover request-handling logic, permission checks, and patch validation across your deployed versions.
Test Your Web App

Frequently Asked Questions

What is CVE-2024-25065?

A path traversal flaw in Apache OFBiz before 18.12.12. It lets an attacker bypass authentication through the contextPath variable inside the hasBasePermission() method. Crafted path segments like ./ and ../ in the request path reach protected views without valid permissions.

Which versions of Apache OFBiz are affected?

All releases before 18.12.12. Version 18.12.12 contains the patch that normalizes the contextPath value. For testing the bug, 18.12.05 builds with the fewest issues.

How does the contextPath authentication bypass work?

hasBasePermission() used the raw contextPath to decide access, and it was never normalized. By inserting relative segments such as . and .. an attacker changes the resolved path so the permission check passes for a view that should require authentication. The request then hits a protected screen without proper rights.

How was CVE-2024-25065 fixed?

The patch in LoginWorker.java validates and normalizes contextPath into a standard URI string before the permission check runs. Normalization strips redundant segments, resolves relative paths like . and .., and converts the value to a canonical form. After this, traversal sequences no longer alter the resolved path.

What is needed to build OFBiz for testing this CVE?

OpenJDK 8 is required; jdk8u41 works for 18.12.05. Build with ./gradlew cleanAll loadAll, and supply gradle-wrapper.jar via gradle wrapper if it is missing. Run in debug mode with ./gradlew ofbiz –debug-jvm, which exposes the debug port on 5005; unset _JAVA_OPTIONS if it blocks startup.