Skip to content

Commit 4bbd072

Browse files
dkayiwaclaude
andauthored
Use PipeParser for inbound HL7 to remove XML/XXE attack surface (#6319)
Backport of #6223 to 2.9.x. The hL7Parser bean was a HAPI GenericParser, which auto-detects message encoding and routes XML-encoded payloads to HAPI's XML parser. On hapi-base 2.1 that parser builds an unhardened LSParser (no disallow-doctype-decl, external general/parameter entities enabled), exposing XML external entity (XXE) processing: external file reads, SSRF and entity-expansion DoS. Inbound HL7 parsing runs before authorization, so the surface is reachable pre-auth. OpenMRS only ever produces and consumes pipe-delimited (ER7) HL7v2, so the XML branch is unused. Switch the bean to PipeParser, which rejects XML payloads outright with an unsupported-encoding error. The HL7ServiceImpl parser field and setter are widened to the common Parser supertype so the existing XML setter injection still resolves. Adds a regression test. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent a490d5f commit 4bbd072

3 files changed

Lines changed: 29 additions & 4 deletions

File tree

api/src/main/java/org/openmrs/hl7/impl/HL7ServiceImpl.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
import ca.uhn.hl7v2.model.v25.segment.NK1;
4242
import ca.uhn.hl7v2.model.v25.segment.PID;
4343
import ca.uhn.hl7v2.parser.EncodingNotSupportedException;
44-
import ca.uhn.hl7v2.parser.GenericParser;
44+
import ca.uhn.hl7v2.parser.Parser;
4545
import org.apache.commons.lang3.StringUtils;
4646
import org.apache.commons.lang3.exception.ExceptionUtils;
4747
import org.openmrs.Location;
@@ -88,7 +88,7 @@ public class HL7ServiceImpl extends BaseOpenmrsService implements HL7Service {
8888

8989
protected HL7DAO dao;
9090

91-
private GenericParser parser;
91+
private Parser parser;
9292

9393
private MessageTypeRouter router;
9494

@@ -105,7 +105,7 @@ public void setHL7DAO(HL7DAO dao) {
105105
*
106106
* @param parser the parser to use
107107
*/
108-
public void setParser(GenericParser parser) {
108+
public void setParser(Parser parser) {
109109
this.parser = parser;
110110
}
111111

api/src/main/resources/applicationContext-service.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,10 @@
297297
<bean id="hL7ServiceTarget" class="org.openmrs.hl7.impl.HL7ServiceImpl">
298298
<property name="HL7DAO" ref="hL7DAO"/>
299299
<property name="parser">
300-
<bean class="ca.uhn.hl7v2.parser.GenericParser"/>
300+
<!-- PipeParser (ER7 only), not GenericParser: rejects XML-encoded payloads outright
301+
rather than routing them to HAPI's XML parser, which can expose XXE (external file
302+
reads, SSRF, entity-expansion DoS) when its JAXP factory is not hardened. -->
303+
<bean class="ca.uhn.hl7v2.parser.PipeParser"/>
301304
</property>
302305
<property name="router">
303306
<bean class="ca.uhn.hl7v2.app.MessageTypeRouter"/>

api/src/test/java/org/openmrs/hl7/HL7ServiceTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,28 @@ public void parseHL7String_shouldParseTheGivenStringIntoMessage() throws HL7Exce
186186
+ "OBX|2|DT|5096^RETURN VISIT DATE^99DCT||20080229|||||||||20080212");
187187
assertNotNull(message);
188188
}
189+
190+
/**
191+
* The {@code hL7Parser} bean is a {@link ca.uhn.hl7v2.parser.PipeParser} (not a
192+
* {@code GenericParser}), so an XML-encoded payload is rejected outright with an unsupported
193+
* encoding error instead of being routed to HAPI's XML parser, which can expose XML external entity
194+
* (XXE) processing when its underlying JAXP factory is not hardened. This guards against a
195+
* regression that reintroduces XML parsing of inbound HL7. The DOCTYPE/external entity below is
196+
* never resolved because the message is refused before any XML processing occurs.
197+
*
198+
* @see HL7Service#parseHL7String(String)
199+
*/
200+
@Test
201+
public void parseHL7String_shouldRejectXmlEncodedMessages() {
202+
HL7Service hl7service = Context.getHL7Service();
203+
String xmlHl7 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
204+
+ "<!DOCTYPE ORU_R01 [ <!ENTITY xxe SYSTEM \"file:///etc/passwd\"> ]>\n"
205+
+ "<ORU_R01 xmlns=\"urn:hl7-org:v2xml\">\n" + " <MSH><MSH.1>|</MSH.1><MSH.2>^~\\&amp;</MSH.2></MSH>\n"
206+
+ " <PID><PID.5><XPN.1>&xxe;</XPN.1></PID.5></PID>\n" + "</ORU_R01>";
207+
HL7Exception thrown = assertThrows(HL7Exception.class, () -> hl7service.parseHL7String(xmlHl7));
208+
assertEquals("HL7 encoding not supported", thrown.getMessage());
209+
}
210+
189211

190212
/**
191213
* @see HL7Service#processHL7Message(Message)

0 commit comments

Comments
 (0)