Skip to content

Prevent XXE in the HL7 v2.x strict parser - #408

Merged
tonygermano merged 2 commits into
OpenIntegrationEngine:mainfrom
pacmano1:fix/hl7v2-strict-parser-xxe
Sep 24, 2026
Merged

tonygermano merged 2 commits into
OpenIntegrationEngine:mainfrom
pacmano1:fix/hl7v2-strict-parser-xxe

Conversation

@pacmano1

@pacmano1 pacmano1 commented Aug 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The HL7 v2.x strict parser hands XML-encoded inbound messages to the bundled HAPI 2.3, whose XMLUtils.parse resolves external XML entities with no hardening. On a channel with Use Strict Parser + Validate in Strict Parser, an unauthenticated message to the MLLP/TCP listener could trigger SSRF (internal services, cloud metadata) and local file disclosure. Reported as oie-1; also independently reported by Samuel Paschuan.

Fix

ER7Serializer.CustomDefaultXMLParser.parseStringIntoDocument — the sole path from OIE to the vulnerable parser — is overridden to reject DOCTYPE declarations, matching the disallow-doctype-decl hardening already used on the sibling fromXML path (and ~20 other XML parse sites in the tree).

This is a minimal, non-breaking change. The format the strict parser handles — the official HL7 v2.xml encoding (urn:hl7-org:v2xml) — is defined by XML Schema, not DTDs, so a standards-conformant message does not use a DOCTYPE and is unaffected. (This is distinct from Mirth's own non-strict <HL7Message> format, which is a separate code path the override does not touch, and which is already DOCTYPE-hardened on the fromXML side.) Any inbound message that does carry a DOCTYPE is now rejected rather than parsed — that is the intended hardening, since at the parser level a DOCTYPE-bearing message is indistinguishable from the attack. Existing channels need no changes: no new options, no migration, no configuration.

How this was verified

  • Confirmed the vulnerability at the bytecode level. Disassembled hapi-base-2.3.jar: XMLUtils sets no doctype or external-entity restrictions.
  • Confirmed the fix is comprehensive at a single chokepoint. parseStringIntoDocument is the only caller of XMLUtils.parse across all HAPI parser classes, and both of OIE's parser instances (serialization + deserialization) are the overridden CustomDefaultXMLParser — it is the only HAPI XML parser instantiated in the server/donkey tree.
  • Live MLLP before/after (the authoritative proof). Imported the xxe-poc channel (MLLP :6661, strict + strict validation), sent the report's payload against an out-of-band HTTP catcher:
    • Unpatched build: the catcher logged the callback — SSRF fired, unauthenticated.
    • Patched build: DOCTYPE is disallowed — the message is rejected and the server never calls out.

Tests

Unit tests fire the report's payload on the strict toXML path and assert the parse is rejected with a DOCTYPE-disallowed root cause, alongside a benign v2.xml message that still round-trips (the hardening does not break valid XML).

A CDATA field in a strict-parsed v2.xml message keeps its value. This is covered by testToXmlStrictValidatingKeepsCdataContent and by smoke case 111-hl7-strict-xml-cdata, which checks the stored transformed message on a running engine.

Honest caveat: the DOCTYPE-rejection unit test asserts the intended behavior but does not by itself discriminate patched from unpatched, because the test-JVM's default XML parser rejects a DOCTYPE regardless — the engine runtime is where HAPI resolves a different, vulnerable parser. The discriminating before/after evidence is the live MLLP reproduction above.

Follow-ups (separate)

  • HAPI upgrade — the hapi-structures 2.3 → 2.6.0 bump is tracked by Renovate in the Dependency Dashboard (Dependency Dashboard #386); the vendored hapi-base jar needs replacing alongside it. This override should stay even after the upgrade: HAPI's 2.4+ fix leaves disallow-doctype-decl=false (permits a DOCTYPE, only disables entity resolution), so this refusal is the stronger posture.
  • Optional surface reduction — an "Allow XML" data type toggle to let a strict HL7 channel refuse XML input entirely (mgaffigan's Prevent XML xxe on Strict HL7 parsing #406) is a separate enhancement, not a security control, and is deliberately out of scope here.
  • A distinct XXE in the generic XML data type's batch adaptor (XMLBatchAdaptor parses inbound content with an unhardened XPathFactory) was found during this work and will be filed and fixed separately.

Credits

Thanks to Samuel Paschuan for reporting the issue, and to Michael Gaffigan (@mgaffigan) and Tony Germano for the design discussion.

@github-actions

github-actions Bot commented Aug 10, 2026 •

Copy link
Copy Markdown

Test Results

126 files  ± 0  126 suites  ±0   3m 12s ⏱️ + 1m 15s
717 tests + 4  717 ✅ + 4  0 💤 ±0  0 ❌ ±0 
765 runs  +10  759 ✅ +10  6 💤 ±0  0 ❌ ±0 

Results for commit 03eefcf. ± Comparison against base commit 864ede7.

♻️ This comment has been updated with latest results.

jonbartels
jonbartels previously approved these changes Sep 23, 2026
@jonbartels
jonbartels force-pushed the fix/hl7v2-strict-parser-xxe branch from fcac881 to 9682f76 Compare September 23, 2026 17:57
@jonbartels jonbartels added the 4.6.x CVE Fixes A quick way to organize a batch of CVE issues for 4.6.x, Sept 2026 label Sep 23, 2026
mgaffigan
mgaffigan previously approved these changes Sep 24, 2026

@gibson9583 gibson9583 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

silently drops CDATA content. Its replacement parser in ER7Serializer.java (~line 540) doesn't call setCoalescing(true)

The strict parser hands XML-encoded inbound messages to HAPI 2.3, whose
XMLUtils.parse resolves external XML entities. On a channel with the strict
parser and strict validation enabled, an unauthenticated message to the MLLP/TCP
listener could trigger SSRF and local file disclosure.

Override CustomDefaultXMLParser.parseStringIntoDocument -- the sole path to the
vulnerable parse -- to reject DOCTYPE declarations, matching the
disallow-doctype-decl hardening already used on the fromXML path. Legitimate HL7
v2.xml is schema-based and never carries a DOCTYPE, so no valid message is
affected and the strict parser keeps accepting XML as before.

Verified with a live MLLP reproduction (xxe-poc): the unpatched build fetched the
attacker URL; the patched build rejects the message and never calls out.

Signed-off-by: Finnegan's Owner <44065187+pacmano1@users.noreply.github.com>
The DOCTYPE-rejecting parser override did not coalesce CDATA into text
nodes. HAPI reads only text nodes into a field, so a field sent as CDATA
was silently emptied, where HAPI's own parser kept it. Enable coalescing.

Adds a unit test and a smoke case (111-hl7-strict-xml-cdata) that sends a
v2.xml message with a CDATA field through a strict-parser channel.

Signed-off-by: Finnegan's Owner <44065187+pacmano1@users.noreply.github.com>
@pacmano1
pacmano1 dismissed stale reviews from mgaffigan and jonbartels via 03eefcf September 24, 2026 18:49
@pacmano1
pacmano1 force-pushed the fix/hl7v2-strict-parser-xxe branch from 9682f76 to 03eefcf Compare September 24, 2026 18:49
@pacmano1

Copy link
Copy Markdown
Contributor Author

Reproduced on a running engine. A strict-parser channel given <HD.1><![CDATA[CDATA-APP]]></HD.1> stored the transformed message without MSH.3 and still marked it SENT. HAPI's own parser keeps the value, so the override caused it. Fixed with setCoalescing(true) in 03eefcf, with a unit test that fails without it and a smoke case, 111-hl7-strict-xml-cdata. Also rebased onto main.

@pacmano1
pacmano1 requested a review from gibson9583 September 24, 2026 19:22
@tonygermano
tonygermano merged commit 03eefcf into OpenIntegrationEngine:main Sep 24, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4.6.x CVE Fixes A quick way to organize a batch of CVE issues for 4.6.x, Sept 2026

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants