Disable DTD when parsing untrusted XML input

Issue: SPR-13136
This commit is contained in:
Rossen Stoyanchev
2015-06-30 07:54:24 -04:00
parent a837cbe81f
commit 9c3580d04e
12 changed files with 476 additions and 56 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,6 +37,7 @@ import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.xml.XMLConstants;
@@ -177,6 +178,8 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
private Schema schema;
private boolean supportDtd = false;
private boolean processExternalEntities = false;
@@ -391,6 +394,21 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
this.mappedClass = mappedClass;
}
/**
* Indicates whether DTD parsing should be supported.
* <p>Default is {@code false} meaning that DTD is disabled.
*/
public void setSupportDtd(boolean supportDtd) {
this.supportDtd = supportDtd;
}
/**
* Whether DTD parsing is supported.
*/
public boolean isSupportDtd() {
return this.supportDtd;
}
/**
* Indicates whether external XML entities are processed when unmarshalling.
* <p>Default is {@code false}, meaning that external entities are not resolved.
@@ -398,9 +416,14 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
* {@code Source} passed to {@link #unmarshal(Source)} is a {@link SAXSource} or
* {@link StreamSource}. It has no effect for {@link DOMSource} or {@link StAXSource}
* instances.
* <p><strong>Note:</strong> setting this option to {@code true} also
* automatically sets {@link #setSupportDtd} to {@code true}.
*/
public void setProcessExternalEntities(boolean processExternalEntities) {
this.processExternalEntities = processExternalEntities;
if (processExternalEntities) {
setSupportDtd(true);
}
}
/**
@@ -410,6 +433,7 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
return this.processExternalEntities;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
@@ -754,6 +778,14 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
return unmarshaller.unmarshal(source);
}
}
catch (NullPointerException ex) {
if (!isSupportDtd()) {
throw new UnmarshallingFailureException("NPE while unmarshalling. " +
"This can happen on JDK 1.6 due to the presence of DTD " +
"declarations, which are disabled.", ex);
}
throw ex;
}
catch (JAXBException ex) {
throw convertJaxbException(ex);
}
@@ -809,6 +841,7 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
if (xmlReader == null) {
xmlReader = XMLReaderFactory.createXMLReader();
}
xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
String name = "http://xml.org/sax/features/external-general-entities";
xmlReader.setFeature(name, isProcessExternalEntities());
if (!isProcessExternalEntities()) {

View File

@@ -72,6 +72,8 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
private boolean supportDtd = false;
private boolean processExternalEntities = false;
private DocumentBuilderFactory documentBuilderFactory;
@@ -79,6 +81,21 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
private final Object documentBuilderFactoryMonitor = new Object();
/**
* Indicates whether DTD parsing should be supported.
* <p>Default is {@code false} meaning that DTD is disabled.
*/
public void setSupportDtd(boolean supportDtd) {
this.supportDtd = supportDtd;
}
/**
* Whether DTD parsing is supported.
*/
public boolean isSupportDtd() {
return this.supportDtd;
}
/**
* Indicates whether external XML entities are processed when unmarshalling.
* <p>Default is {@code false}, meaning that external entities are not resolved.
@@ -86,9 +103,14 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
* {@code Source} passed to {@link #unmarshal(Source)} is a {@link SAXSource} or
* {@link StreamSource}. It has no effect for {@link DOMSource} or {@link StAXSource}
* instances.
* <p><strong>Note:</strong> setting this option to {@code true} also
* automatically sets {@link #setSupportDtd} to {@code true}.
*/
public void setProcessExternalEntities(boolean processExternalEntities) {
this.processExternalEntities = processExternalEntities;
if (processExternalEntities) {
setSupportDtd(true);
}
}
/**
@@ -133,6 +155,8 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
factory.setNamespaceAware(true);
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
factory.setFeature("http://xml.org/sax/features/external-general-entities", isProcessExternalEntities());
return factory;
}
@@ -147,7 +171,11 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory)
throws ParserConfigurationException {
return factory.newDocumentBuilder();
DocumentBuilder documentBuilder = factory.newDocumentBuilder();
if (!isProcessExternalEntities()) {
documentBuilder.setEntityResolver(NO_OP_ENTITY_RESOLVER);
}
return documentBuilder;
}
/**
@@ -157,6 +185,7 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
*/
protected XMLReader createXmlReader() throws SAXException {
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", isProcessExternalEntities());
if (!isProcessExternalEntities()) {
xmlReader.setEntityResolver(NO_OP_ENTITY_RESOLVER);
@@ -343,7 +372,17 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
if (domSource.getNode() == null) {
domSource.setNode(buildDocument());
}
return unmarshalDomNode(domSource.getNode());
try {
return unmarshalDomNode(domSource.getNode());
}
catch (NullPointerException ex) {
if (!isSupportDtd()) {
throw new UnmarshallingFailureException("NPE while unmarshalling. " +
"This can happen on JDK 1.6 due to the presence of DTD " +
"declarations, which are disabled.", ex);
}
throw ex;
}
}
/**
@@ -391,7 +430,17 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
if (saxSource.getInputSource() == null) {
saxSource.setInputSource(new InputSource());
}
return unmarshalSaxReader(saxSource.getXMLReader(), saxSource.getInputSource());
try {
return unmarshalSaxReader(saxSource.getXMLReader(), saxSource.getInputSource());
}
catch (NullPointerException ex) {
if (!isSupportDtd()) {
throw new UnmarshallingFailureException("NPE while unmarshalling. " +
"This can happen on JDK 1.6 due to the presence of DTD " +
"declarations, which are disabled.");
}
throw ex;
}
}
/**
@@ -404,7 +453,7 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
*/
protected Object unmarshalStreamSource(StreamSource streamSource) throws XmlMappingException, IOException {
if (streamSource.getInputStream() != null) {
if (isProcessExternalEntities()) {
if (isProcessExternalEntities() && isSupportDtd()) {
return unmarshalInputStream(streamSource.getInputStream());
}
else {
@@ -414,7 +463,7 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
}
}
else if (streamSource.getReader() != null) {
if (isProcessExternalEntities()) {
if (isProcessExternalEntities() && isSupportDtd()) {
return unmarshalReader(streamSource.getReader());
}
else {