Java XXE Prevention
Comprehensive guide to preventing XXE vulnerabilities in Java applications using secure XML parser configurations across different libraries.
Overview
Java applications are particularly susceptible to XXE attacks because many XML parsers have external entity processing enabled by default. This guide covers secure configuration for the most common Java XML parsing libraries:
- DocumentBuilderFactory (DOM)
- SAXParserFactory (SAX)
- XMLInputFactory (StAX)
- TransformerFactory (XSLT)
- SchemaFactory
- XMLReader
The key principle: Explicitly disable all dangerous features before parsing any untrusted XML.
DocumentBuilderFactory (DOM Parsing)
DocumentBuilderFactory is the most common way to parse XML in Java. Here's the secure configuration:
Secure DocumentBuilderFactory
1import javax.xml.parsers.DocumentBuilder;
2import javax.xml.parsers.DocumentBuilderFactory;
3import javax.xml.parsers.ParserConfigurationException;
4
5public class SecureDocumentBuilder {
6 public static DocumentBuilder createSecureBuilder()
7 throws ParserConfigurationException {
8 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
9
10 // This is the PRIMARY defense. If DTDs (doctypes) are disallowed,
11 // almost all XXE attacks are prevented
12 String FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
13 dbf.setFeature(FEATURE, true);
14
15 // If you can't completely disable DOCTYPE, then at least do these:
16 // Disable external general entities
17 dbf.setFeature(
18 "http://xml.org/sax/features/external-general-entities",
19 false
20 );
21
22 // Disable external parameter entities
23 dbf.setFeature(
24 "http://xml.org/sax/features/external-parameter-entities",
25 false
26 );
27
28 // Disable external DTDs
29 dbf.setFeature(
30 "http://apache.org/xml/features/nonvalidating/load-external-dtd",
31 false
32 );
33
34 // Disable XInclude processing
35 dbf.setXIncludeAware(false);
36
37 // Disable entity expansion
38 dbf.setExpandEntityReferences(false);
39
40 return dbf.newDocumentBuilder();
41 }
42}SAXParserFactory (SAX Parsing)
SAX parsers are event-driven and also need secure configuration:
Secure SAXParserFactory
1import javax.xml.parsers.SAXParser;
2import javax.xml.parsers.SAXParserFactory;
3import javax.xml.parsers.ParserConfigurationException;
4import org.xml.sax.SAXException;
5
6public class SecureSAXParser {
7 public static SAXParser createSecureParser()
8 throws ParserConfigurationException, SAXException {
9 SAXParserFactory spf = SAXParserFactory.newInstance();
10
11 // Disable DOCTYPE declarations
12 spf.setFeature(
13 "http://apache.org/xml/features/disallow-doctype-decl",
14 true
15 );
16
17 // Disable external general entities
18 spf.setFeature(
19 "http://xml.org/sax/features/external-general-entities",
20 false
21 );
22
23 // Disable external parameter entities
24 spf.setFeature(
25 "http://xml.org/sax/features/external-parameter-entities",
26 false
27 );
28
29 // Disable loading external DTD
30 spf.setFeature(
31 "http://apache.org/xml/features/nonvalidating/load-external-dtd",
32 false
33 );
34
35 return spf.newSAXParser();
36 }
37}XMLInputFactory (StAX Parsing)
StAX (Streaming API for XML) also requires secure configuration:
Secure XMLInputFactory
1import javax.xml.stream.XMLInputFactory;
2import javax.xml.stream.XMLStreamException;
3
4public class SecureStAXParser {
5 public static XMLInputFactory createSecureFactory() {
6 XMLInputFactory xif = XMLInputFactory.newFactory();
7
8 // Disable DTDs entirely
9 xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
10
11 // Disable external entities
12 xif.setProperty(
13 XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES,
14 false
15 );
16
17 return xif;
18 }
19}TransformerFactory (XSLT)
XSLT transformers can also be exploited for XXE attacks:
Secure TransformerFactory
1import javax.xml.transform.TransformerFactory;
2import javax.xml.transform.TransformerConfigurationException;
3import javax.xml.XMLConstants;
4
5public class SecureTransformer {
6 public static TransformerFactory createSecureFactory()
7 throws TransformerConfigurationException {
8 TransformerFactory tf = TransformerFactory.newInstance();
9
10 // Disable access to external DTDs and stylesheets
11 tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
12 tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
13
14 // For older Java versions, use:
15 tf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
16
17 return tf;
18 }
19}SchemaFactory (XML Schema)
Schema validation can also be vulnerable:
Secure SchemaFactory
1import javax.xml.validation.SchemaFactory;
2import javax.xml.XMLConstants;
3import org.xml.sax.SAXException;
4
5public class SecureSchema {
6 public static SchemaFactory createSecureFactory()
7 throws SAXException {
8 SchemaFactory sf = SchemaFactory.newInstance(
9 XMLConstants.W3C_XML_SCHEMA_NS_URI
10 );
11
12 // Disable access to external DTDs and schemas
13 sf.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
14 sf.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
15
16 return sf;
17 }
18}XMLReader
XMLReader also needs secure configuration:
Secure XMLReader
1import org.xml.sax.XMLReader;
2import org.xml.sax.SAXException;
3import javax.xml.parsers.SAXParserFactory;
4import javax.xml.parsers.ParserConfigurationException;
5
6public class SecureXMLReader {
7 public static XMLReader createSecureReader()
8 throws SAXException, ParserConfigurationException {
9 SAXParserFactory spf = SAXParserFactory.newInstance();
10
11 spf.setFeature(
12 "http://apache.org/xml/features/disallow-doctype-decl",
13 true
14 );
15 spf.setFeature(
16 "http://xml.org/sax/features/external-general-entities",
17 false
18 );
19 spf.setFeature(
20 "http://xml.org/sax/features/external-parameter-entities",
21 false
22 );
23
24 XMLReader reader = spf.newSAXParser().getXMLReader();
25 return reader;
26 }
27}Complete Secure Example
Here's a complete example showing secure XML parsing in a real application:
Production-Ready Secure Parser
1import javax.xml.parsers.DocumentBuilder;
2import javax.xml.parsers.DocumentBuilderFactory;
3import org.w3c.dom.Document;
4import java.io.InputStream;
5import java.util.logging.Logger;
6
7public class ProductionXMLParser {
8 private static final Logger LOGGER =
9 Logger.getLogger(ProductionXMLParser.class.getName());
10
11 private final DocumentBuilder builder;
12
13 public ProductionXMLParser() throws Exception {
14 this.builder = createSecureBuilder();
15 }
16
17 private DocumentBuilder createSecureBuilder() throws Exception {
18 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
19
20 try {
21 // PRIMARY DEFENSE: Disable DOCTYPE
22 dbf.setFeature(
23 "http://apache.org/xml/features/disallow-doctype-decl",
24 true
25 );
26
27 // SECONDARY DEFENSES (in case DOCTYPE can't be disabled)
28 dbf.setFeature(
29 "http://xml.org/sax/features/external-general-entities",
30 false
31 );
32 dbf.setFeature(
33 "http://xml.org/sax/features/external-parameter-entities",
34 false
35 );
36 dbf.setFeature(
37 "http://apache.org/xml/features/nonvalidating/load-external-dtd",
38 false
39 );
40
41 // Additional security settings
42 dbf.setXIncludeAware(false);
43 dbf.setExpandEntityReferences(false);
44
45 LOGGER.info("Secure XML parser initialized successfully");
46
47 } catch (Exception e) {
48 LOGGER.severe("Failed to configure secure XML parser: "
49 + e.getMessage());
50 throw new Exception(
51 "Could not create secure XML parser", e
52 );
53 }
54
55 return dbf.newDocumentBuilder();
56 }
57
58 public Document parseXML(InputStream xmlInput) throws Exception {
59 try {
60 return builder.parse(xmlInput);
61 } catch (Exception e) {
62 LOGGER.severe("XML parsing failed: " + e.getMessage());
63 throw new Exception("Invalid XML input", e);
64 }
65 }
66}Testing Your Configuration
Always test that your configuration actually prevents XXE attacks:
Unit Test for XXE Prevention
1import org.junit.Test;
2import static org.junit.Assert.*;
3
4public class XMLParserTest {
5
6 @Test(expected = Exception.class)
7 public void testXXEIsBlocked() throws Exception {
8 // This XXE payload should be rejected
9 String xxePayload =
10 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
11 "<!DOCTYPE foo [" +
12 " <!ENTITY xxe SYSTEM \"file:///etc/passwd\">" +
13 "]>" +
14 "<root>" +
15 " <data>&xxe;</data>" +
16 "</root>";
17
18 ProductionXMLParser parser = new ProductionXMLParser();
19
20 // This should throw an exception due to DOCTYPE
21 parser.parseXML(
22 new ByteArrayInputStream(xxePayload.getBytes())
23 );
24
25 fail("XXE payload was not blocked!");
26 }
27
28 @Test
29 public void testValidXMLIsParsed() throws Exception {
30 String validXml =
31 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
32 "<root>" +
33 " <data>Hello World</data>" +
34 "</root>";
35
36 ProductionXMLParser parser = new ProductionXMLParser();
37 Document doc = parser.parseXML(
38 new ByteArrayInputStream(validXml.getBytes())
39 );
40
41 assertNotNull(doc);
42 }
43}Common Mistakes to Avoid
Mistake 1: Partial Configuration ā Only disabling one feature ā Disable ALL dangerous features
Mistake 2: Wrong Feature Names ā Using incorrect feature URIs ā Copy exact feature names from this guide
Mistake 3: Not Testing ā Assuming configuration works without testing ā Write unit tests with XXE payloads
Mistake 4: Catching and Ignoring Exceptions
ā try { setFeature(...) } catch (Exception e) {}
ā Let configuration failures propagate
Mistake 5: Using Vulnerable Libraries ā Using outdated XML libraries ā Keep dependencies up to date
Quick Reference Summary
For DocumentBuilderFactory:
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
For SAXParserFactory:
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
For XMLInputFactory:
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
For TransformerFactory:
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
Remember: The single most effective defense is disallow-doctype-decl.