From dcb278173a35bbcd5c7dc775bf2607db68e31b61 Mon Sep 17 00:00:00 2001 From: Mitch Gaffigan Date: Mon, 10 Aug 2026 10:06:37 -0500 Subject: [PATCH] Prevent XML xxe on HL7 parsing Signed-off-by: Mitch Gaffigan --- .../datatypes/hl7v2/ER7Serializer.java | 2 +- .../hl7v2/HL7v2SerializationProperties.java | 15 ++++++++ .../datatypes/hl7v2/ER7SerializerTest.java | 37 ++++++++++++++++++- 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/mirth/connect/plugins/datatypes/hl7v2/ER7Serializer.java b/server/src/main/java/com/mirth/connect/plugins/datatypes/hl7v2/ER7Serializer.java index 71e9a49b54..985ce4a758 100644 --- a/server/src/main/java/com/mirth/connect/plugins/datatypes/hl7v2/ER7Serializer.java +++ b/server/src/main/java/com/mirth/connect/plugins/datatypes/hl7v2/ER7Serializer.java @@ -199,7 +199,7 @@ public String toXML(String source) throws MessageSerializerException { Message message = null; source = source.trim(); - if (source.length() > 0 && source.charAt(0) == '<') { + if (serializationProperties.isAllowXml() && source.length() > 0 && source.charAt(0) == '<') { if (serializationProperties.isUseStrictValidation()) { // If the message is XML and strict validation is needed, we'll need to create a message to be encoded. message = serializationXmlParser.parse(source); diff --git a/server/src/main/java/com/mirth/connect/plugins/datatypes/hl7v2/HL7v2SerializationProperties.java b/server/src/main/java/com/mirth/connect/plugins/datatypes/hl7v2/HL7v2SerializationProperties.java index 3d788c5dff..aeaa9288b1 100644 --- a/server/src/main/java/com/mirth/connect/plugins/datatypes/hl7v2/HL7v2SerializationProperties.java +++ b/server/src/main/java/com/mirth/connect/plugins/datatypes/hl7v2/HL7v2SerializationProperties.java @@ -23,6 +23,7 @@ public class HL7v2SerializationProperties extends SerializationProperties { private boolean handleSubcomponents = true; private boolean useStrictParser = false; private boolean useStrictValidation = false; + private boolean allowXml = false; private boolean stripNamespaces = false; private String segmentDelimiter = "\\r"; private boolean convertLineBreaks = true; @@ -35,6 +36,7 @@ public Map getPropertyDescriptors() { properties.put("handleSubcomponents", new DataTypePropertyDescriptor(handleSubcomponents, "Parse Subcomponents", "Parse subcomponents (applies to Non-Strict Parser only).", PropertyEditorType.BOOLEAN)); properties.put("useStrictParser", new DataTypePropertyDescriptor(useStrictParser, "Use Strict Parser", "Parse messages based upon strict HL7 specifications.", PropertyEditorType.BOOLEAN)); properties.put("useStrictValidation", new DataTypePropertyDescriptor(useStrictValidation, "Validate in Strict Parser", "Validate messages using HL7 specifications (applies to Strict Parser only).", PropertyEditorType.BOOLEAN)); + properties.put("allowXml", new DataTypePropertyDescriptor(allowXml, "Allow XML Messages", "Allow input that appears to be XML-formatted to be parsed as HL7-XML instead of being rejected as invalid ER7 (applies to Strict Parser only).", PropertyEditorType.BOOLEAN)); properties.put("stripNamespaces", new DataTypePropertyDescriptor(stripNamespaces, "Strip Namespaces", "Strips namespace definitions from the transformed XML message (applies to Strict Parser only).", PropertyEditorType.BOOLEAN)); properties.put("segmentDelimiter", new DataTypePropertyDescriptor(segmentDelimiter, "Segment Delimiter", "This is the input delimiter character(s) expected to occur after each segment.", PropertyEditorType.STRING)); properties.put("convertLineBreaks", new DataTypePropertyDescriptor(convertLineBreaks, "Convert Line Breaks", "Convert all styles of line breaks (CRLF, CR, LF) in the raw message to the segment delimiter. ", PropertyEditorType.BOOLEAN)); @@ -61,6 +63,10 @@ public void setProperties(Map properties) { this.useStrictValidation = (Boolean) properties.get("useStrictValidation"); } + if (properties.get("allowXml") != null) { + this.allowXml = (Boolean) properties.get("allowXml"); + } + if (properties.get("stripNamespaces") != null) { this.stripNamespaces = (Boolean) properties.get("stripNamespaces"); } @@ -107,6 +113,14 @@ public void setUseStrictValidation(boolean useStrictValidation) { this.useStrictValidation = useStrictValidation; } + public boolean isAllowXml() { + return allowXml; + } + + public void setAllowXml(boolean allowXml) { + this.allowXml = allowXml; + } + public boolean isStripNamespaces() { return stripNamespaces; } @@ -152,6 +166,7 @@ public Map getPurgedProperties() { purgedProperties.put("handleRepetitions", handleRepetitions); purgedProperties.put("handleSubcomponents", handleSubcomponents); purgedProperties.put("useStrictParser", useStrictParser); + purgedProperties.put("allowXml", allowXml); purgedProperties.put("stripNamespaces", stripNamespaces); purgedProperties.put("convertLineBreaks", convertLineBreaks); return purgedProperties; diff --git a/server/src/test/java/com/mirth/connect/plugins/datatypes/hl7v2/ER7SerializerTest.java b/server/src/test/java/com/mirth/connect/plugins/datatypes/hl7v2/ER7SerializerTest.java index a7319dca5a..37eec91378 100644 --- a/server/src/test/java/com/mirth/connect/plugins/datatypes/hl7v2/ER7SerializerTest.java +++ b/server/src/test/java/com/mirth/connect/plugins/datatypes/hl7v2/ER7SerializerTest.java @@ -1,5 +1,6 @@ package com.mirth.connect.plugins.datatypes.hl7v2; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -15,11 +16,22 @@ public class ER7SerializerTest { private static ER7Serializer serializer; - + private static ER7Serializer strictSerializer; + private static ER7Serializer strictSerializerAllowXml; + @BeforeClass public static void setupClass() throws Exception { SerializerProperties serializerProperties = new SerializerProperties(new HL7v2SerializationProperties(), new HL7v2DeserializationProperties(), null); serializer = new ER7Serializer(serializerProperties); + + HL7v2SerializationProperties strictProperties = new HL7v2SerializationProperties(); + strictProperties.setUseStrictParser(true); + strictSerializer = new ER7Serializer(new SerializerProperties(strictProperties, new HL7v2DeserializationProperties(), null)); + + HL7v2SerializationProperties strictPropertiesAllowXml = new HL7v2SerializationProperties(); + strictPropertiesAllowXml.setUseStrictParser(true); + strictPropertiesAllowXml.setAllowXml(true); + strictSerializerAllowXml = new ER7Serializer(new SerializerProperties(strictPropertiesAllowXml, new HL7v2DeserializationProperties(), null)); } @Test @@ -53,4 +65,27 @@ public void testValidFromXMLWithExternalDTD() throws Exception { assertFalse(exceptionThrown); } + + @Test + public void testToXmlWithStrictParserRejectsXmlInputByDefault() throws Exception { + String xmlDisguisedAsHl7 = "notreallyhl7"; + + boolean exceptionThrown = false; + try { + strictSerializer.toXML(xmlDisguisedAsHl7); + } catch (MessageSerializerException e) { + exceptionThrown = true; + } + + assertTrue(exceptionThrown); + } + + @Test + public void testToXmlWithStrictParserAllowsXmlInputWhenOptedIn() throws Exception { + String xmlDisguisedAsHl7 = "notreallyhl7"; + + String result = strictSerializerAllowXml.toXML(xmlDisguisedAsHl7); + + assertEquals(xmlDisguisedAsHl7, result); + } }