Default Parser Configuration
XMLInputFactory and other XML parsers are configured with external entity processing enabled by default, making applications vulnerable unless developers explicitly disable these features.
XML External Entity (XXE) vulnerabilities occur when XML parsers process external entity references without proper security controls. This allows attackers to read local files, perform server-side request forgery (SSRF), cause denial of service, and potentially execute remote code by exploiting the XML parser's ability to resolve external entities.
// VULNERABLE: XMLInputFactory with default settings
import javax.xml.stream.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
import java.io.*;
import java.util.*;
// VULNERABLE: XMLInputFactory without security settings
public class VulnerableXMLProcessor {
public void processXMLStream(String xmlContent) throws XMLStreamException {
// VULNERABLE: Default XMLInputFactory allows external entities
XMLInputFactory factory = XMLInputFactory.newInstance();
StringReader reader = new StringReader(xmlContent);
XMLStreamReader xmlReader = factory.createXMLStreamReader(reader);
while (xmlReader.hasNext()) {
int event = xmlReader.next();
if (event == XMLStreamConstants.START_ELEMENT) {
String elementName = xmlReader.getLocalName();
System.out.println("Element: " + elementName);
// VULNERABLE: Processing element content without validation
if (xmlReader.hasText()) {
String text = xmlReader.getElementText();
System.out.println("Content: " + text);
}
}
}
xmlReader.close();
}
// VULNERABLE: DocumentBuilder without security settings
public void parseXMLDocument(String xmlContent) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// VULNERABLE: Default settings allow external entities
DocumentBuilder builder = factory.newDocumentBuilder();
StringReader reader = new StringReader(xmlContent);
InputSource inputSource = new InputSource(reader);
// VULNERABLE: Parse XML with external entity processing enabled
Document document = builder.parse(inputSource);
// Process document
processDocument(document);
}
// VULNERABLE: SAXParser without security settings
public void parseXMLWithSAX(String xmlContent) throws Exception {
SAXParserFactory factory = SAXParserFactory.newInstance();
// VULNERABLE: Default SAX parser settings
SAXParser parser = factory.newSAXParser();
DefaultHandler handler = new DefaultHandler() {
@Override
public void startElement(String uri, String localName,
String qName, Attributes attributes) {
System.out.println("SAX Element: " + qName);
}
@Override
public void characters(char[] ch, int start, int length) {
String content = new String(ch, start, length).trim();
if (!content.isEmpty()) {
System.out.println("SAX Content: " + content);
}
}
};
StringReader reader = new StringReader(xmlContent);
InputSource inputSource = new InputSource(reader);
// VULNERABLE: Parse with external entities enabled
parser.parse(inputSource, handler);
}
// VULNERABLE: XML processing for configuration files
public Properties loadConfigFromXML(String xmlConfig) throws Exception {
Properties config = new Properties();
// VULNERABLE: Process XML configuration without security
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new InputSource(new StringReader(xmlConfig)));
NodeList properties = doc.getElementsByTagName("property");
for (int i = 0; i < properties.getLength(); i++) {
Element property = (Element) properties.item(i);
String key = property.getAttribute("name");
String value = property.getTextContent();
// VULNERABLE: External entity content loaded as config
config.setProperty(key, value);
}
return config;
}
// VULNERABLE: XML-based data import
public List<User> importUsersFromXML(InputStream xmlStream) throws Exception {
List<User> users = new ArrayList<>();
// VULNERABLE: XMLInputFactory for user data import
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader reader = factory.createXMLStreamReader(xmlStream);
String currentElement = "";
User currentUser = null;
while (reader.hasNext()) {
int event = reader.next();
switch (event) {
case XMLStreamConstants.START_ELEMENT:
currentElement = reader.getLocalName();
if ("user".equals(currentElement)) {
currentUser = new User();
}
break;
case XMLStreamConstants.CHARACTERS:
String text = reader.getText().trim();
if (!text.isEmpty() && currentUser != null) {
switch (currentElement) {
case "username":
// VULNERABLE: External entity content as username
currentUser.setUsername(text);
break;
case "email":
currentUser.setEmail(text);
break;
case "role":
currentUser.setRole(text);
break;
}
}
break;
case XMLStreamConstants.END_ELEMENT:
if ("user".equals(reader.getLocalName()) && currentUser != null) {
users.add(currentUser);
currentUser = null;
}
break;
}
}
reader.close();
return users;
}
private void processDocument(Document document) {
// Process DOM document
NodeList elements = document.getElementsByTagName("*");
for (int i = 0; i < elements.getLength(); i++) {
Node node = elements.item(i);
System.out.println("Processing: " + node.getNodeName());
}
}
}
// VULNERABLE: User class for data import
class User {
private String username;
private String email;
private String role;
// Getters and setters
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getRole() { return role; }
public void setRole(String role) { this.role = role; }
}
// VULNERABLE: Example usage that would be exploited
public class VulnerableXMLExample {
public static void main(String[] args) {
VulnerableXMLProcessor processor = new VulnerableXMLProcessor();
// VULNERABLE: Malicious XML with external entity
String maliciousXML =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<!DOCTYPE root [" +
" <!ENTITY xxe SYSTEM \"file:///etc/passwd\">" +
"]>" +
"<root>" +
" <data>&xxe;</data>" + // This would read /etc/passwd
"</root>";
try {
// VULNERABLE: This would expose file contents
processor.processXMLStream(maliciousXML);
} catch (Exception e) {
e.printStackTrace();
}
}
}// SECURE: XMLInputFactory with proper security configuration
import javax.xml.stream.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
import java.io.*;
import java.util.*;
import java.util.regex.Pattern;
// SECURE: XML processor with comprehensive security settings
public class SecureXMLProcessor {
// SECURE: Create secure XMLInputFactory
private XMLInputFactory createSecureXMLInputFactory() {
XMLInputFactory factory = XMLInputFactory.newInstance();
// SECURE: Disable external entity processing
factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
factory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
// SECURE: Additional security properties
try {
factory.setProperty("javax.xml.stream.isSupportingExternalEntities", false);
factory.setProperty("javax.xml.stream.supportDTD", false);
} catch (IllegalArgumentException e) {
// Some implementations may not support these properties
}
return factory;
}
public void processXMLStream(String xmlContent) throws XMLStreamException {
// Input validation
if (!isValidXMLContent(xmlContent)) {
throw new XMLStreamException("Invalid or potentially malicious XML content");
}
// SECURE: Use secure XMLInputFactory
XMLInputFactory factory = createSecureXMLInputFactory();
StringReader reader = new StringReader(xmlContent);
XMLStreamReader xmlReader = null;
try {
xmlReader = factory.createXMLStreamReader(reader);
while (xmlReader.hasNext()) {
int event = xmlReader.next();
if (event == XMLStreamConstants.START_ELEMENT) {
String elementName = xmlReader.getLocalName();
// SECURE: Validate element names
if (isValidElementName(elementName)) {
System.out.println("Element: " + elementName);
// SECURE: Safe text processing
String text = getSecureElementText(xmlReader);
if (text != null) {
System.out.println("Content: " + sanitizeText(text));
}
}
}
}
} finally {
if (xmlReader != null) {
xmlReader.close();
}
}
}
// SECURE: DocumentBuilder with security settings
public void parseXMLDocument(String xmlContent) throws Exception {
// Input validation
if (!isValidXMLContent(xmlContent)) {
throw new Exception("Invalid or potentially malicious XML content");
}
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// SECURE: Disable external entity processing
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);
// SECURE: Additional security features
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);
DocumentBuilder builder = factory.newDocumentBuilder();
// SECURE: Custom error handler
builder.setErrorHandler(new SecurityErrorHandler());
StringReader reader = new StringReader(xmlContent);
InputSource inputSource = new InputSource(reader);
// SECURE: Parse with security restrictions
Document document = builder.parse(inputSource);
// SECURE: Process document safely
processDocumentSecurely(document);
}
// SECURE: SAXParser with security configuration
public void parseXMLWithSAX(String xmlContent) throws Exception {
if (!isValidXMLContent(xmlContent)) {
throw new Exception("Invalid or potentially malicious XML content");
}
SAXParserFactory factory = SAXParserFactory.newInstance();
// SECURE: Disable external entity processing
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);
SAXParser parser = factory.newSAXParser();
// SECURE: Custom content handler with validation
DefaultHandler handler = new SecureContentHandler();
StringReader reader = new StringReader(xmlContent);
InputSource inputSource = new InputSource(reader);
// SECURE: Parse with security restrictions
parser.parse(inputSource, handler);
}
// SECURE: Configuration loading with validation
public Properties loadConfigFromXML(String xmlConfig) throws Exception {
// SECURE: Validate configuration content
if (!isValidConfigXML(xmlConfig)) {
throw new Exception("Invalid configuration XML");
}
Properties config = new Properties();
// SECURE: Use secure document builder
DocumentBuilderFactory factory = createSecureDocumentBuilderFactory();
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setErrorHandler(new SecurityErrorHandler());
Document doc = builder.parse(new InputSource(new StringReader(xmlConfig)));
NodeList properties = doc.getElementsByTagName("property");
for (int i = 0; i < properties.getLength(); i++) {
Element property = (Element) properties.item(i);
String key = sanitizeConfigKey(property.getAttribute("name"));
String value = sanitizeConfigValue(property.getTextContent());
// SECURE: Validate configuration values
if (isValidConfigProperty(key, value)) {
config.setProperty(key, value);
}
}
return config;
}
// SECURE: Data import with comprehensive validation
public List<User> importUsersFromXML(InputStream xmlStream) throws Exception {
List<User> users = new ArrayList<>();
// SECURE: Pre-validate stream content
String xmlContent = readStreamSafely(xmlStream);
if (!isValidUserDataXML(xmlContent)) {
throw new Exception("Invalid user data XML format");
}
XMLInputFactory factory = createSecureXMLInputFactory();
XMLStreamReader reader = null;
try {
reader = factory.createXMLStreamReader(new StringReader(xmlContent));
String currentElement = "";
User currentUser = null;
int userCount = 0;
final int MAX_USERS = 10000; // Prevent DoS
while (reader.hasNext() && userCount < MAX_USERS) {
int event = reader.next();
switch (event) {
case XMLStreamConstants.START_ELEMENT:
currentElement = reader.getLocalName();
if ("user".equals(currentElement)) {
currentUser = new User();
userCount++;
}
break;
case XMLStreamConstants.CHARACTERS:
String text = reader.getText().trim();
if (!text.isEmpty() && currentUser != null) {
// SECURE: Validate and sanitize user data
text = sanitizeUserData(text);
switch (currentElement) {
case "username":
if (isValidUsername(text)) {
currentUser.setUsername(text);
}
break;
case "email":
if (isValidEmail(text)) {
currentUser.setEmail(text);
}
break;
case "role":
if (isValidRole(text)) {
currentUser.setRole(text);
}
break;
}
}
break;
case XMLStreamConstants.END_ELEMENT:
if ("user".equals(reader.getLocalName()) && currentUser != null) {
// SECURE: Validate complete user object
if (isValidUser(currentUser)) {
users.add(currentUser);
}
currentUser = null;
}
break;
}
}
} finally {
if (reader != null) {
reader.close();
}
}
return users;
}
// SECURE: Helper methods for security validation
private boolean isValidXMLContent(String xmlContent) {
if (xmlContent == null || xmlContent.trim().isEmpty()) {
return false;
}
// Check for suspicious patterns
String upperContent = xmlContent.toUpperCase();
// SECURE: Detect potential XXE patterns
if (upperContent.contains("<!DOCTYPE") ||
upperContent.contains("<!ENTITY") ||
upperContent.contains("SYSTEM") ||
upperContent.contains("PUBLIC") ||
upperContent.contains("&") && upperContent.contains(";")) {
return false;
}
// Check for excessive size
if (xmlContent.length() > 1024 * 1024) { // 1MB limit
return false;
}
return true;
}
private DocumentBuilderFactory createSecureDocumentBuilderFactory() throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// SECURE: Comprehensive security configuration
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);
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);
return factory;
}
private String readStreamSafely(InputStream stream) throws IOException {
StringBuilder content = new StringBuilder();
byte[] buffer = new byte[8192];
int totalBytes = 0;
final int MAX_SIZE = 10 * 1024 * 1024; // 10MB limit
int bytesRead;
while ((bytesRead = stream.read(buffer)) != -1) {
totalBytes += bytesRead;
if (totalBytes > MAX_SIZE) {
throw new IOException("XML content exceeds maximum allowed size");
}
content.append(new String(buffer, 0, bytesRead, "UTF-8"));
}
return content.toString();
}
private String getSecureElementText(XMLStreamReader reader) throws XMLStreamException {
if (reader.hasText()) {
String text = reader.getElementText();
return (text.length() > 10000) ? text.substring(0, 10000) : text;
}
return null;
}
private String sanitizeText(String text) {
if (text == null) return "";
// Remove potentially dangerous characters
return text.replaceAll("[<>&\"']", "")
.replaceAll("\\p{Cntrl}", "") // Remove control characters
.trim();
}
private String sanitizeUserData(String data) {
if (data == null) return "";
return data.replaceAll("[<>&\"'\n\r\t]", "")
.replaceAll("\\p{Cntrl}", "")
.trim();
}
// Validation methods
private boolean isValidElementName(String name) {
return name != null &&
name.matches("[a-zA-Z][a-zA-Z0-9_-]*") &&
name.length() <= 50;
}
private boolean isValidUsername(String username) {
return username != null &&
username.matches("[a-zA-Z0-9_-]+") &&
username.length() >= 3 &&
username.length() <= 50;
}
private boolean isValidEmail(String email) {
return email != null &&
email.matches("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}") &&
email.length() <= 254;
}
private boolean isValidRole(String role) {
Set<String> validRoles = Set.of("user", "admin", "moderator", "viewer");
return role != null && validRoles.contains(role.toLowerCase());
}
private boolean isValidUser(User user) {
return user != null &&
user.getUsername() != null &&
user.getEmail() != null &&
user.getRole() != null;
}
private boolean isValidConfigXML(String xml) {
return isValidXMLContent(xml) &&
xml.contains("<property") &&
!xml.contains("<!ENTITY");
}
private boolean isValidUserDataXML(String xml) {
return isValidXMLContent(xml) &&
xml.contains("<user") &&
!xml.contains("<!ENTITY");
}
private String sanitizeConfigKey(String key) {
if (key == null) return "";
return key.replaceAll("[^a-zA-Z0-9._-]", "").toLowerCase();
}
private String sanitizeConfigValue(String value) {
if (value == null) return "";
return value.replaceAll("[<>&\"']", "").trim();
}
private boolean isValidConfigProperty(String key, String value) {
return key != null && !key.isEmpty() &&
value != null &&
key.length() <= 100 &&
value.length() <= 1000;
}
private void processDocumentSecurely(Document document) {
NodeList elements = document.getElementsByTagName("*");
int nodeCount = 0;
final int MAX_NODES = 10000;
for (int i = 0; i < elements.getLength() && nodeCount < MAX_NODES; i++) {
Node node = elements.item(i);
String nodeName = sanitizeText(node.getNodeName());
if (isValidElementName(nodeName)) {
System.out.println("Processing: " + nodeName);
nodeCount++;
}
}
}
}
// SECURE: Custom error handler for XML parsing
class SecurityErrorHandler implements ErrorHandler {
@Override
public void warning(SAXParseException exception) throws SAXException {
throw new SAXException("XML parsing warning: " + exception.getMessage());
}
@Override
public void error(SAXParseException exception) throws SAXException {
throw new SAXException("XML parsing error: " + exception.getMessage());
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw new SAXException("XML parsing fatal error: " + exception.getMessage());
}
}
// SECURE: Custom content handler with validation
class SecureContentHandler extends DefaultHandler {
private int elementCount = 0;
private final int MAX_ELEMENTS = 10000;
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
if (++elementCount > MAX_ELEMENTS) {
throw new SAXException("Too many XML elements - potential DoS attack");
}
// Validate element name
if (!isValidElementName(qName)) {
throw new SAXException("Invalid element name: " + qName);
}
System.out.println("SAX Element: " + qName);
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
String content = new String(ch, start, length).trim();
if (!content.isEmpty()) {
// Validate and sanitize content
if (content.length() > 10000) {
throw new SAXException("Element content too large");
}
String sanitized = content.replaceAll("[<>&\"']", "");
System.out.println("SAX Content: " + sanitized);
}
}
private boolean isValidElementName(String name) {
return name != null &&
name.matches("[a-zA-Z][a-zA-Z0-9_-]*") &&
name.length() <= 50;
}
}
// SECURE: User class with validation
class User {
private String username;
private String email;
private String role;
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getRole() { return role; }
public void setRole(String role) { this.role = role; }
@Override
public String toString() {
return String.format("User{username='%s', email='%s', role='%s'}",
username, email, role);
}
}
// SECURE: Example usage demonstrating security
public class SecureXMLExample {
public static void main(String[] args) {
SecureXMLProcessor processor = new SecureXMLProcessor();
// SECURE: Clean XML without external entities
String safeXML =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<root>" +
" <data>Safe content here</data>" +
" <users>" +
" <user>" +
" <username>john_doe</username>" +
" <email>john@example.com</email>" +
" <role>user</role>" +
" </user>" +
" </users>" +
"</root>";
try {
// SECURE: This will process safely
System.out.println("Processing safe XML:");
processor.processXMLStream(safeXML);
// SECURE: Malicious XML will be rejected
String maliciousXML =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<!DOCTYPE root [" +
" <!ENTITY xxe SYSTEM \"file:///etc/passwd\">" +
"]>" +
"<root>" +
" <data>&xxe;</data>" +
"</root>";
System.out.println("\nAttempting to process malicious XML:");
processor.processXMLStream(maliciousXML);
} catch (Exception e) {
System.out.println("SECURE: Malicious XML was blocked: " + e.getMessage());
}
}
}The vulnerable examples show XML parsers using default configurations that allow external entity processing, making them susceptible to XXE attacks for file disclosure and SSRF. The secure alternatives implement comprehensive security measures including disabling external entities, DTD processing, validating input content, sanitizing data, implementing size limits, and using custom error handlers to prevent XXE vulnerabilities.
XMLInputFactory and other XML parsers are configured with external entity processing enabled by default, making applications vulnerable unless developers explicitly disable these features.
Sourcery automatically identifies xml external entity (xxe) injection via xmlinputfactory external entities and many other security issues in your codebase.