CVE-2026-94109 Overview
CVE-2026-94109 is an authenticated stored server-side template injection (SSTI) vulnerability in openEQUELLA versions before 2026.1.0. The flaw resides in FreemarkerPortletRenderer.renderHtml(), where the markup field of a stored portlet configuration is passed directly to custFactory.createResult() without a TemplateClassResolver restriction or FreeMarker sandboxing in BasicConfiguration. Any authenticated non-guest user can store a malicious FreeMarker payload via a POST request to the RemotePortletService invoker endpoint. When any user renders a dashboard containing the affected portlet, the payload executes in the application server process context, achieving remote code execution. The issue is categorized under CWE-1336 (Improper Neutralization of Special Elements Used in a Template Engine).
Critical Impact
Any authenticated non-guest user can achieve remote code execution on the openEQUELLA application server by storing a malicious FreeMarker template that executes when a victim renders the dashboard.
Affected Products
- openEQUELLA versions prior to 2026.1.0
- Deployments exposing the RemotePortletService invoker endpoint to authenticated users
- Any dashboard configuration allowing portlet markup persistence
Discovery Timeline
- 2026-09-20 - CVE-2026-94109 published to NVD
- 2026-09-24 - Last updated in NVD database
Technical Details for CVE-2026-94109
Vulnerability Analysis
The vulnerability exists in openEQUELLA's FreeMarker portlet rendering pipeline. The BasicConfiguration class extends FreeMarker's Configuration but omits any TemplateClassResolver restriction. This leaves dangerous built-ins such as ?new and freemarker.template.utility.Execute fully reachable at template evaluation time. When FreemarkerPortletRenderer.renderHtml() invokes custFactory.createResult(), the persisted markup string from the portlet is compiled and executed as a FreeMarker template.
Because the portlet's markup field is user-controlled and stored, the attack is second-order. An authenticated user submits the payload once through the RemotePortletService invoker endpoint. Any subsequent dashboard render, including by higher-privileged users, triggers execution in the application server JVM context.
Root Cause
The root cause is missing template engine hardening. FreeMarker ships with sandboxing primitives such as TemplateClassResolver.SAFER_RESOLVER, but BasicConfiguration did not apply them. Combined with unvalidated storage of user-supplied template markup, this exposes the full FreeMarker built-in surface to attackers.
Attack Vector
An attacker with any authenticated non-guest account issues a POST request to the RemotePortletService invoker endpoint containing a FreeMarker payload in the portlet's markup field. The payload is stored server-side. When a user loads a dashboard that includes the malicious portlet, the template engine evaluates built-ins like freemarker.template.utility.Execute?new()("cmd"), executing arbitrary operating system commands under the openEQUELLA service account.
// Security patch: Source/Plugins/Core/com.equella.core/src/com/tle/web/freemarker/BasicConfiguration.java
// OEQ-3000 - fix(security): lock down FreeMarker to prevent SSTI/RCE
import com.tle.core.guice.Bind;
import com.tle.web.DebugSettings;
import freemarker.cache.NullCacheStorage;
+import freemarker.core.TemplateClassResolver;
import freemarker.template.Configuration;
import javax.inject.Singleton;
-@SuppressWarnings("nls")
@Bind
@Singleton
public class BasicConfiguration extends Configuration {
Source: GitHub Commit d6e165af
Detection Methods for CVE-2026-94109
Indicators of Compromise
- POST requests to the RemotePortletService invoker endpoint containing FreeMarker syntax such as <#assign, ?new, or freemarker.template.utility.Execute in the markup field.
- Unexpected child processes spawned by the openEQUELLA application server JVM, particularly shells (sh, bash, cmd.exe) or reconnaissance binaries.
- Newly created or modified portlet configurations authored by low-privilege accounts.
- Outbound network connections initiated by the application server process to attacker-controlled infrastructure.
Detection Strategies
- Inspect application and web server access logs for POST bodies to portlet service endpoints containing FreeMarker directives or Java class references.
- Monitor the openEQUELLA JVM for anomalous process lineage using endpoint telemetry.
- Audit stored portlet markup fields in the openEQUELLA database for FreeMarker built-ins that reference Execute, ObjectConstructor, or ?new.
Monitoring Recommendations
- Enable verbose request logging on the RemotePortletService endpoint and forward to a centralized analytics platform.
- Alert on child process creation from the openEQUELLA Java process using EDR behavioral rules.
- Track dashboard render events tied to newly created portlets and correlate with the authoring user's privilege level.
How to Mitigate CVE-2026-94109
Immediate Actions Required
- Upgrade openEQUELLA to version 2026.1.0 or later, which applies the TemplateClassResolver restriction in BasicConfiguration.
- Review all stored portlets and remove any containing suspicious FreeMarker directives or references to freemarker.template.utility.Execute.
- Rotate credentials for any accounts that may have been used to author malicious portlets and audit privileged session activity.
Patch Information
The fix is delivered in openEQUELLA Release 2026.1.0. The corresponding source change is tracked in GitHub Commit d6e165af under ticket OEQ-3000. Additional analysis is available in the VulnCheck Advisory on SSTI and the Evan Lat Blog Post.
Workarounds
- Restrict access to the RemotePortletService invoker endpoint via network ACLs or reverse-proxy rules until patching is complete.
- Limit portlet creation and editing rights to trusted administrators only.
- Deploy a web application firewall rule to block request bodies containing FreeMarker built-ins such as ?new and freemarker.template.utility.Execute against portlet service paths.
# Example WAF rule (ModSecurity) to block FreeMarker SSTI payloads to portlet endpoints
SecRule REQUEST_URI "@contains RemotePortletService" \
"phase:2,deny,status:403,id:1094109,\
chain,msg:'Potential FreeMarker SSTI (CVE-2026-94109)'"
SecRule REQUEST_BODY "@rx (freemarker\.template\.utility\.Execute|\?new\(\))" "t:none,t:lowercase"
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.