Skip to content

CWE-611: XML External Entity (XXE) Injection - Java

Overview

XXE vulnerabilities occur when XML parsers process external entity references in untrusted XML, allowing attackers to read files, perform SSRF attacks, or cause denial of service. Java's XML parsers are vulnerable by default and must be explicitly configured securely.

Primary Defence: Disable DTDs entirely by setting FEATURE_SECURE_PROCESSING to true and disabling external entities in DocumentBuilderFactory, SAXParserFactory, or XMLReader with appropriate feature flags.

Common Vulnerable Patterns

DocumentBuilderFactory (Default Configuration)

// VULNERABLE - Default settings allow XXE
import javax.xml.parsers.*;
import org.w3c.dom.*;

public class XmlParser {
    public Document parse(String xml) throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();

        // DANGEROUS: Allows external entities by default
        return builder.parse(new InputSource(new StringReader(xml)));
    }
}

// Attacker sends:
// <?xml version="1.0"?>
// <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
// <root>&xxe;</root>

Why this is vulnerable:

  • External entities are enabled by default.
  • Enables file disclosure, SSRF, and entity expansion DoS.

SAXParserFactory (Default Configuration)

// VULNERABLE - SAX parser without security
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

public void parseSax(String xml) throws Exception {
    SAXParserFactory factory = SAXParserFactory.newInstance();
    SAXParser parser = factory.newSAXParser();

    // DANGEROUS: External entities enabled
    parser.parse(new InputSource(new StringReader(xml)), new DefaultHandler());
}

Why this is vulnerable:

  • DTDs/external entities are processed by default.
  • Enables file disclosure, SSRF, and DoS.

XMLReader (Default Configuration)

// VULNERABLE - XMLReader without features
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

public void parseXml(String xml) throws Exception {
    XMLReader reader = XMLReaderFactory.createXMLReader();

    // DANGEROUS: No security features set
    reader.parse(new InputSource(new StringReader(xml)));
}

Why this is vulnerable:

  • DTDs and entities are processed without feature flags.
  • Enables file:// disclosure, SSRF, and resource exhaustion.

Unmarshaller (JAXB)

// VULNERABLE - JAXB without secure XML input factory
import javax.xml.bind.*;

public User unmarshal(String xml) throws Exception {
    JAXBContext context = JAXBContext.newInstance(User.class);
    Unmarshaller unmarshaller = context.createUnmarshaller();

    // DANGEROUS: Uses default XML parser
    return (User) unmarshaller.unmarshal(new StringReader(xml));
}

Why this is vulnerable:

  • Default parser allows external entities.
  • Enables file disclosure and SSRF via crafted XML.

Secure Patterns

DocumentBuilderFactory (Secure Configuration)

// SECURE - Disable all dangerous features
import javax.xml.parsers.*;
import javax.xml.XMLConstants;
import org.w3c.dom.*;

public class SecureXmlParser {
    public Document parse(String xml) throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

        // Disable DTDs entirely
        factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);

        // If DTDs must be allowed, disable external entities
        factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

        // Disable external DTDs
        factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);

        // Disable XInclude
        factory.setXIncludeAware(false);

        // Disable entity expansion
        factory.setExpandEntityReferences(false);

        DocumentBuilder builder = factory.newDocumentBuilder();
        return builder.parse(new InputSource(new StringReader(xml)));
    }
}

Why this works:

  • Rejects DOCTYPE and disables external entities/DTDs.
  • Disables XInclude and entity expansion to prevent DoS.

SAXParserFactory (Secure Configuration)

// SECURE - SAX parser with security features
import javax.xml.parsers.*;
import org.xml.sax.*;

public class SecureSaxParser {
    public void parse(String xml, DefaultHandler handler) throws Exception {
        SAXParserFactory factory = SAXParserFactory.newInstance();

        // Disable DTDs
        factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);

        // Disable external entities
        factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

        // Disable external DTD loading
        factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);

        SAXParser parser = factory.newSAXParser();
        XMLReader reader = parser.getXMLReader();

        // Set secure entity resolver
        reader.setEntityResolver((publicId, systemId) -> {
            throw new SAXException("External entities are not allowed");
        });

        reader.setContentHandler(handler);
        reader.parse(new InputSource(new StringReader(xml)));
    }
}

Why this works:

  • Feature flags disable DOCTYPE and external entity processing.
  • EntityResolver fail-safe blocks any resolution attempts.

XMLReader (Secure Configuration)

// SECURE - XMLReader with all protections
import org.xml.sax.*;
import org.xml.sax.helpers.XMLReaderFactory;

public void parseSecure(String xml) throws Exception {
    XMLReader reader = XMLReaderFactory.createXMLReader();

    // Disable DTDs
    reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);

    // Disable external entities
    reader.setFeature("http://xml.org/sax/features/external-general-entities", false);
    reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

    // Secure entity resolver
    reader.setEntityResolver((publicId, systemId) -> {
        throw new SAXException("External entities are blocked");
    });

    reader.parse(new InputSource(new StringReader(xml)));
}

Why this works:

  • Disables DOCTYPE and entity processing at the SAX level.
  • EntityResolver blocks any resolution attempts.

XMLInputFactory (StAX Parser - Secure Configuration)

// SECURE - StAX parser with external entity restrictions
import javax.xml.stream.*;

public void parseWithStax(String xml) throws Exception {
    XMLInputFactory factory = XMLInputFactory.newFactory();

    // Disable DTD processing entirely
    factory.setProperty(XMLInputFactory.SUPPORT_DTD, false);

    // Disable external entity processing
    factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);

    try (StringReader reader = new StringReader(xml)) {
        XMLStreamReader streamReader = factory.createXMLStreamReader(reader);

        while (streamReader.hasNext()) {
            int event = streamReader.next();
            if (event == XMLStreamConstants.START_ELEMENT) {
                String elementName = streamReader.getLocalName();
                // Process elements securely
            }
        }
        streamReader.close();
    }
}

Why this works:

  • DTD support and external entities are disabled.
  • StAX remains safe and streaming-friendly for large inputs.

XPath with Secure Parsing

// SECURE - XPath expression with secure document parsing
import javax.xml.xpath.*;
import javax.xml.XMLConstants;

public String evaluateXPath(String xml, String xpathExpression) throws Exception {
    // First parse XML securely
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);

    // Restrict external access
    dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
    dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");

    DocumentBuilder builder = dbf.newDocumentBuilder();
    Document doc = builder.parse(new ByteArrayInputStream(xml.getBytes("UTF-8")));

    // Now XPath evaluation is safe
    XPathFactory xpathFactory = XPathFactory.newInstance();
    XPath xpath = xpathFactory.newXPath();
    XPathExpression expr = xpath.compile(xpathExpression);

    return expr.evaluate(doc);
}

Why this works:

  • Secure parsing blocks DOCTYPE and external access first.
  • XPath runs on a pre-parsed safe DOM.

JAXB (Secure Configuration)

// SECURE - JAXB with secure XML input factory
import javax.xml.bind.*;
import javax.xml.parsers.*;
import javax.xml.transform.sax.SAXSource;

public User unmarshalSecure(String xml) throws Exception {
    // Create secure SAX parser
    SAXParserFactory spf = SAXParserFactory.newInstance();
    spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
    spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
    spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

    SAXParser saxParser = spf.newSAXParser();
    XMLReader reader = saxParser.getXMLReader();

    // Use secure reader with JAXB
    JAXBContext context = JAXBContext.newInstance(User.class);
    Unmarshaller unmarshaller = context.createUnmarshaller();

    SAXSource source = new SAXSource(reader, new InputSource(new StringReader(xml)));
    return (User) unmarshaller.unmarshal(source);
}

Why this works:

  • Uses a secure SAX parser instead of JAXB defaults.
  • Blocks DOCTYPE and external entities before unmarshalling.

Framework-Specific Guidance

Spring Framework

// SECURE - Spring XML configuration
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.beans.factory.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class XmlConfig {

    @Bean
    public Jaxb2Marshaller marshaller() {
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        marshaller.setClassesToBeBound(User.class);

        // Configure secure properties
        Map<String, Object> properties = new HashMap<>();
        properties.put("jaxb.encoding", "UTF-8");

        marshaller.setMarshallerProperties(properties);

        // Set secure XML input factory
        marshaller.setProcessExternalEntities(false);

        return marshaller;
    }
}

// Controller usage:
@RestController
public class UserController {

    @Autowired
    private Jaxb2Marshaller marshaller;

    @PostMapping(value = "/users", consumes = "application/xml")
    public ResponseEntity<User> createUser(@RequestBody String xml) throws Exception {
        // Create secure SAX source
        SAXParserFactory spf = SAXParserFactory.newInstance();
        spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);

        SAXParser parser = spf.newSAXParser();
        XMLReader reader = parser.getXMLReader();

        SAXSource source = new SAXSource(reader, new InputSource(new StringReader(xml)));

        User user = (User) marshaller.unmarshal(source);
        return ResponseEntity.ok(user);
    }
}

JAX-RS (RESTful Web Services)

// SECURE - JAX-RS with secure XML
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.xml.parsers.*;

@Path("/users")
@Produces(MediaType.APPLICATION_XML)
@Consumes(MediaType.APPLICATION_XML)
public class UserResource {

    @POST
    public Response createUser(String xml) throws Exception {
        // Parse with secure DocumentBuilder
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

        DocumentBuilder builder = factory.newDocumentBuilder();
        Document doc = builder.parse(new InputSource(new StringReader(xml)));

        // Extract data from DOM
        String name = doc.getElementsByTagName("name").item(0).getTextContent();
        String email = doc.getElementsByTagName("email").item(0).getTextContent();

        User user = new User(name, email);
        userService.save(user);

        return Response.ok(user).build();
    }
}

Reusable Secure Parser Utility

Reusable Secure Parser Utility

// Utility class for secure XML parsing
public class SecureXmlUtil {

    /**
     * Creates a secure DocumentBuilderFactory
     */
    public static DocumentBuilderFactory createSecureDocumentBuilderFactory() throws ParserConfigurationException {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

        // Completely disable DTDs
        factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);

        // If you can't disable DTDs, at least disable external entities
        factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);

        factory.setXIncludeAware(false);
        factory.setExpandEntityReferences(false);

        return factory;
    }

    /**
     * Creates a secure SAXParserFactory
     */
    public static SAXParserFactory createSecureSAXParserFactory() throws Exception {
        SAXParserFactory factory = SAXParserFactory.newInstance();

        factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);

        return factory;
    }

    /**
     * Parse XML string securely to Document
     */
    public static Document parseXml(String xml) throws Exception {
        DocumentBuilderFactory factory = createSecureDocumentBuilderFactory();
        DocumentBuilder builder = factory.newDocumentBuilder();

        // Block all entity resolution
        builder.setEntityResolver((publicId, systemId) -> {
            throw new SAXException("External entities are not allowed");
        });

        return builder.parse(new InputSource(new StringReader(xml)));
    }
}

// Usage:
Document doc = SecureXmlUtil.parseXml(untrustedXml);

Why this works:

  • Centralized hardened factories enforce consistent XXE protection.
  • EntityResolver fail-safe blocks resolution and simplifies reviews.

Input Validation

// Validate XML structure after parsing
import org.w3c.dom.*;

public User parseAndValidate(String xml) throws Exception {
    Document doc = SecureXmlUtil.parseXml(xml);

    // Validate structure
    NodeList users = doc.getElementsByTagName("user");
    if (users.getLength() != 1) {
        throw new IllegalArgumentException("Expected exactly one user element");
    }

    Element userEl = (Element) users.item(0);

    String name = getElementText(userEl, "name");
    String email = getElementText(userEl, "email");

    // Validate content
    if (name == null || name.isEmpty() || name.length() > 100) {
        throw new IllegalArgumentException("Invalid name");
    }

    if (email == null || !email.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$")) {
        throw new IllegalArgumentException("Invalid email");
    }

    return new User(name, email);
}

private String getElementText(Element parent, String tagName) {
    NodeList nodes = parent.getElementsByTagName(tagName);
    if (nodes.getLength() == 0) return null;
    return nodes.item(0).getTextContent();
}

Verification

After implementing the recommended secure patterns, verify the fix through multiple approaches:

  • Manual testing: Submit malicious payloads relevant to this vulnerability and confirm they're handled safely without executing unintended operations
  • Code review: Confirm all instances use the secure pattern (parameterized queries, safe APIs, proper encoding) with no string concatenation or unsafe operations
  • Static analysis: Use security scanners to verify no new vulnerabilities exist and the original finding is resolved
  • Regression testing: Ensure legitimate user inputs and application workflows continue to function correctly
  • Edge case validation: Test with special characters, boundary conditions, and unusual inputs to verify proper handling
  • Framework verification: If using a framework or library, confirm the recommended APIs are used correctly according to documentation
  • Authentication/session testing: Verify security controls remain effective and cannot be bypassed (if applicable to the vulnerability type)
  • Rescan: Run the security scanner again to confirm the finding is resolved and no new issues were introduced

Additional Resources