VYPR
Moderate severityNVD Advisory· Published Apr 17, 2014· Updated May 6, 2026

CVE-2014-0054

CVE-2014-0054

Description

The Jaxb2RootElementHttpMessageConverter in Spring MVC in Spring Framework before 3.2.8 and 4.0.0 before 4.0.2 does not disable external entity resolution, which allows remote attackers to read arbitrary files, cause a denial of service, and conduct CSRF attacks via crafted XML, aka an XML External Entity (XXE) issue. NOTE: this vulnerability exists because of an incomplete fix for CVE-2013-4152, CVE-2013-7315, and CVE-2013-6429.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.springframework:spring-webmvcMaven
< 3.2.83.2.8
org.springframework:spring-webmvcMaven
>= 4.0.0, < 4.0.24.0.2

Affected products

34
  • cpe:2.3:a:springsource:spring_framework:3.0.0:*:*:*:*:*:*:*+ 18 more
    • cpe:2.3:a:springsource:spring_framework:3.0.0:*:*:*:*:*:*:*
    • cpe:2.3:a:springsource:spring_framework:3.0.0.m1:*:*:*:*:*:*:*
    • cpe:2.3:a:springsource:spring_framework:3.0.0:m1:*:*:*:*:*:*
    • cpe:2.3:a:springsource:spring_framework:3.0.0.m2:*:*:*:*:*:*:*
    • cpe:2.3:a:springsource:spring_framework:3.0.0:m2:*:*:*:*:*:*
    • cpe:2.3:a:springsource:spring_framework:3.0.0:m3:*:*:*:*:*:*
    • cpe:2.3:a:springsource:spring_framework:3.0.0:m4:*:*:*:*:*:*
    • cpe:2.3:a:springsource:spring_framework:3.0.0:rc1:*:*:*:*:*:*
    • cpe:2.3:a:springsource:spring_framework:3.0.0:rc2:*:*:*:*:*:*
    • cpe:2.3:a:springsource:spring_framework:3.0.0:rc3:*:*:*:*:*:*
    • cpe:2.3:a:springsource:spring_framework:3.0.1:*:*:*:*:*:*:*
    • cpe:2.3:a:springsource:spring_framework:3.0.2:*:*:*:*:*:*:*
    • cpe:2.3:a:springsource:spring_framework:3.0.3:*:*:*:*:*:*:*
    • cpe:2.3:a:springsource:spring_framework:3.0.4:*:*:*:*:*:*:*
    • cpe:2.3:a:springsource:spring_framework:3.0.5:*:*:*:*:*:*:*
    • cpe:2.3:a:springsource:spring_framework:3.2.5:*:*:*:*:*:*:*
    • cpe:2.3:a:springsource:spring_framework:3.2.6:*:*:*:*:*:*:*
    • cpe:2.3:a:springsource:spring_framework:4.0.0:rc1:*:*:*:*:*:*
    • cpe:2.3:a:springsource:spring_framework:4.0.1:*:*:*:*:*:*:*
  • cpe:2.3:a:vmware:spring_framework:*:*:*:*:*:*:*:*+ 14 more
    • cpe:2.3:a:vmware:spring_framework:*:*:*:*:*:*:*:*range: <=3.2.7
    • cpe:2.3:a:vmware:spring_framework:3.0.6:*:*:*:*:*:*:*
    • cpe:2.3:a:vmware:spring_framework:3.0.7:*:*:*:*:*:*:*
    • cpe:2.3:a:vmware:spring_framework:3.1.0:*:*:*:*:*:*:*
    • cpe:2.3:a:vmware:spring_framework:3.1.1:*:*:*:*:*:*:*
    • cpe:2.3:a:vmware:spring_framework:3.1.2:*:*:*:*:*:*:*
    • cpe:2.3:a:vmware:spring_framework:3.1.3:*:*:*:*:*:*:*
    • cpe:2.3:a:vmware:spring_framework:3.1.4:*:*:*:*:*:*:*
    • cpe:2.3:a:vmware:spring_framework:3.2.0:*:*:*:*:*:*:*
    • cpe:2.3:a:vmware:spring_framework:3.2.1:*:*:*:*:*:*:*
    • cpe:2.3:a:vmware:spring_framework:3.2.2:*:*:*:*:*:*:*
    • cpe:2.3:a:vmware:spring_framework:3.2.3:*:*:*:*:*:*:*
    • cpe:2.3:a:vmware:spring_framework:3.2.4:*:*:*:*:*:*:*
    • cpe:2.3:a:vmware:spring_framework:4.0.0:milestone1:*:*:*:*:*:*
    • cpe:2.3:a:vmware:spring_framework:4.0.0:milestone2:*:*:*:*:*:*

Patches

4
fb0683c066e7

Add processExternalEntities support to OXM

https://github.com/spring-projects/spring-frameworkRossen StoyanchevFeb 19, 2014via ghsa
12 files changed · +327 17
  • spring-oxm/src/main/java/org/springframework/oxm/castor/CastorMarshaller.java+7 2 modified
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2002-2013 the original author or authors.
    + * Copyright 2002-2014 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.
    @@ -162,6 +162,11 @@ public void setEncoding(String encoding) {
     		this.encoding = encoding;
     	}
     
    +	@Override
    +	protected String getDefaultEncoding() {
    +		return this.encoding;
    +	}
    +
     	/**
     	 * Set the locations of the Castor XML mapping files.
     	 */
    @@ -604,7 +609,7 @@ protected final Object unmarshalReader(Reader reader) throws XmlMappingException
     	}
     
     	@Override
    -	protected final Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource)
    +	protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource)
     			throws XmlMappingException, IOException {
     
     		UnmarshalHandler unmarshalHandler = createUnmarshaller().createHandler();
    
  • spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java+8 1 modified
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2002-2013 the original author or authors.
    + * Copyright 2002-2014 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.
    @@ -401,6 +401,13 @@ public void setProcessExternalEntities(boolean processExternalEntities) {
     		this.processExternalEntities = processExternalEntities;
     	}
     
    +	/**
    +	 * @return the configured value for whether XML external entities are allowed.
    +	 */
    +	public boolean isProcessExternalEntities() {
    +		return this.processExternalEntities;
    +	}
    +
     	@Override
     	public void setBeanClassLoader(ClassLoader classLoader) {
     		this.beanClassLoader = classLoader;
    
  • spring-oxm/src/main/java/org/springframework/oxm/jibx/JibxMarshaller.java+14 6 modified
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2002-2013 the original author or authors.
    + * Copyright 2002-2014 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.
    @@ -28,6 +28,7 @@
     import javax.xml.stream.XMLStreamException;
     import javax.xml.stream.XMLStreamReader;
     import javax.xml.stream.XMLStreamWriter;
    +import javax.xml.transform.OutputKeys;
     import javax.xml.transform.Result;
     import javax.xml.transform.Source;
     import javax.xml.transform.Transformer;
    @@ -148,6 +149,11 @@ public void setEncoding(String encoding) {
     		this.encoding = encoding;
     	}
     
    +	@Override
    +	protected String getDefaultEncoding() {
    +		return this.encoding;
    +	}
    +
     	/**
     	 * Set the document standalone flag for marshalling. By default, this flag is not present.
     	 */
    @@ -389,13 +395,12 @@ protected Object unmarshalReader(Reader reader) throws XmlMappingException, IOEx
     		}
     	}
     
    -
     	// Unsupported Unmarshalling
     
     	@Override
     	protected Object unmarshalDomNode(Node node) throws XmlMappingException {
     		try {
    -			return transformAndUnmarshal(new DOMSource(node));
    +			return transformAndUnmarshal(new DOMSource(node), null);
     		}
     		catch (IOException ex) {
     			throw new UnmarshallingFailureException("JiBX unmarshalling exception", ex);
    @@ -406,20 +411,23 @@ protected Object unmarshalDomNode(Node node) throws XmlMappingException {
     	protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource)
     			throws XmlMappingException, IOException {
     
    -		return transformAndUnmarshal(new SAXSource(xmlReader, inputSource));
    +		return transformAndUnmarshal(new SAXSource(xmlReader, inputSource), inputSource.getEncoding());
     	}
     
    -	private Object transformAndUnmarshal(Source source) throws IOException {
    +	private Object transformAndUnmarshal(Source source, String encoding) throws IOException {
     		try {
     			Transformer transformer = this.transformerFactory.newTransformer();
    +			if (encoding != null) {
    +				transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
    +			}
     			ByteArrayOutputStream os = new ByteArrayOutputStream();
     			transformer.transform(source, new StreamResult(os));
     			ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
     			return unmarshalInputStream(is);
     		}
     		catch (TransformerException ex) {
     			throw new MarshallingFailureException(
    -					"Could not transform from [" + ClassUtils.getShortName(source.getClass()) + "]");
    +					"Could not transform from [" + ClassUtils.getShortName(source.getClass()) + "]", ex);
     		}
     	}
     
    
  • spring-oxm/src/main/java/org/springframework/oxm/support/AbstractMarshaller.java+68 4 modified
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2002-2012 the original author or authors.
    + * Copyright 2002-2014 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.
    @@ -73,6 +73,33 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
     
     	private final Object documentBuilderFactoryMonitor = new Object();
     
    +	private boolean processExternalEntities = false;
    +
    +
    +	/**
    +	 * Indicates whether external XML entities are processed when unmarshalling.
    +	 * <p>Default is {@code false}, meaning that external entities are not resolved.
    +	 * Note that processing of external entities will only be enabled/disabled when the
    +	 * {@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.
    +	 */
    +	public void setProcessExternalEntities(boolean processExternalEntities) {
    +		this.processExternalEntities = processExternalEntities;
    +	}
    +
    +	/**
    +	 * @return the configured value for whether XML external entities are allowed.
    +	 */
    +	public boolean isProcessExternalEntities() {
    +		return this.processExternalEntities;
    +	}
    +
    +	/**
    +	 * @return the default encoding to use for marshalling or unmarshalling from
    +	 * 	a byte stream, or {@code null}.
    +	 */
    +	abstract protected String getDefaultEncoding();
     
     	/**
     	 * Marshals the object graph with the given root into the provided {@code javax.xml.transform.Result}.
    @@ -131,7 +158,7 @@ else if (source instanceof SAXSource) {
     			return unmarshalSaxSource((SAXSource) source);
     		}
     		else if (source instanceof StreamSource) {
    -			return unmarshalStreamSource((StreamSource) source);
    +			return unmarshalStreamSourceNoExternalEntitities((StreamSource) source);
     		}
     		else {
     			throw new IllegalArgumentException("Unknown Source type: " + source.getClass());
    @@ -173,7 +200,9 @@ protected DocumentBuilderFactory createDocumentBuilderFactory() throws ParserCon
     	 * @throws SAXException if thrown by JAXP methods
     	 */
     	protected XMLReader createXmlReader() throws SAXException {
    -		return XMLReaderFactory.createXMLReader();
    +		XMLReader xmlReader = XMLReaderFactory.createXMLReader();
    +		xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", isProcessExternalEntities());
    +		return xmlReader;
     	}
     
     
    @@ -355,9 +384,44 @@ protected Object unmarshalSaxSource(SAXSource saxSource) throws XmlMappingExcept
     		return unmarshalSaxReader(saxSource.getXMLReader(), saxSource.getInputSource());
     	}
     
    +	/**
    +	 * Template method for handling {@code StreamSource}s with protection against
    +	 * the XML External Entity (XXE) processing vulnerability taking into account
    +	 * the value of the {@link #setProcessExternalEntities(boolean)} property.
    +	 * <p>
    +	 * The default implementation wraps the StreamSource as a SAXSource and delegates
    +	 * to {@link #unmarshalSaxSource(javax.xml.transform.sax.SAXSource)}.
    +	 *
    +	 * @param streamSource the {@code StreamSource}
    +	 * @return the object graph
    +	 * @throws IOException if an I/O exception occurs
    +	 * @throws XmlMappingException if the given source cannot be mapped to an object
    +	 *
    +	 * @see <a href="https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing">XML_External_Entity_(XXE)_Processing</a>
    +	 */
    +	protected Object unmarshalStreamSourceNoExternalEntitities(StreamSource streamSource)
    +			throws XmlMappingException, IOException {
    +
    +		InputSource inputSource;
    +		if (streamSource.getInputStream() != null) {
    +			inputSource = new InputSource(streamSource.getInputStream());
    +			inputSource.setEncoding(getDefaultEncoding());
    +		}
    +		else if (streamSource.getReader() != null) {
    +			inputSource = new InputSource(streamSource.getReader());
    +		}
    +		else {
    +			inputSource = new InputSource(streamSource.getSystemId());
    +		}
    +		return unmarshalSaxSource(new SAXSource(inputSource));
    +	}
    +
     	/**
     	 * Template method for handling {@code StreamSource}s.
    -	 * <p>This implementation defers to {@code unmarshalInputStream} or {@code unmarshalReader}.
    +	 * <p>As of 3.2.8 and 4.0.2 this method is no longer invoked from
    +	 * {@link #unmarshal(javax.xml.transform.Source)}. The method invoked instead is
    +	 * {@link #unmarshalStreamSourceNoExternalEntitities(javax.xml.transform.stream.StreamSource)}.
    +	 *
     	 * @param streamSource the {@code StreamSource}
     	 * @return the object graph
     	 * @throws IOException if an I/O exception occurs
    
  • spring-oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlBeansMarshaller.java+4 0 modified
    @@ -113,6 +113,10 @@ public boolean isValidating() {
     		return this.validating;
     	}
     
    +	@Override
    +	protected String getDefaultEncoding() {
    +		return null;
    +	}
     
     	/**
     	 * This implementation returns true if the given class is an implementation of {@link XmlObject}.
    
  • spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java+12 1 modified
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2002-2013 the original author or authors.
    + * Copyright 2002-2014 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.
    @@ -31,6 +31,7 @@
     import javax.xml.stream.XMLStreamException;
     import javax.xml.stream.XMLStreamReader;
     import javax.xml.stream.XMLStreamWriter;
    +import javax.xml.transform.stream.StreamSource;
     
     import com.thoughtworks.xstream.XStream;
     import com.thoughtworks.xstream.converters.ConversionException;
    @@ -353,6 +354,11 @@ public void setEncoding(String encoding) {
     		this.encoding = encoding;
     	}
     
    +	@Override
    +	protected String getDefaultEncoding() {
    +		return this.encoding;
    +	}
    +
     	/**
     	 * Set the classes supported by this marshaller.
     	 * <p>If this property is empty (the default), all classes are supported.
    @@ -482,6 +488,11 @@ private void marshal(Object graph, HierarchicalStreamWriter streamWriter) {
     
     	// Unmarshalling
     
    +	@Override
    +	protected Object unmarshalStreamSourceNoExternalEntitities(StreamSource streamSource) throws XmlMappingException, IOException {
    +		return super.unmarshalStreamSource(streamSource);
    +	}
    +
     	@Override
     	protected Object unmarshalDomNode(Node node) throws XmlMappingException {
     		HierarchicalStreamReader streamReader;
    
  • spring-oxm/src/test/java/org/springframework/oxm/castor/CastorUnmarshallerTests.java+60 1 modified
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2002-2013 the original author or authors.
    + * Copyright 2002-2014 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.
    @@ -19,6 +19,8 @@
     import java.io.ByteArrayInputStream;
     import java.io.IOException;
     import java.io.StringReader;
    +import java.util.concurrent.atomic.AtomicReference;
    +import javax.xml.transform.sax.SAXSource;
     import javax.xml.transform.stream.StreamSource;
     
     import org.junit.Ignore;
    @@ -28,6 +30,8 @@
     import org.springframework.oxm.AbstractUnmarshallerTests;
     import org.springframework.oxm.MarshallingException;
     import org.springframework.oxm.Unmarshaller;
    +import org.xml.sax.InputSource;
    +import org.xml.sax.XMLReader;
     
     import static org.hamcrest.CoreMatchers.*;
     import static org.junit.Assert.*;
    @@ -203,4 +207,59 @@ private Object unmarshal(String xml) throws Exception {
     		StreamSource source = new StreamSource(new StringReader(xml));
     		return unmarshaller.unmarshal(source);
     	}
    +
    +	@Test
    +	public void unmarshalStreamSourceExternalEntities() throws Exception {
    +
    +		final AtomicReference<XMLReader> result = new AtomicReference<XMLReader>();
    +		CastorMarshaller marshaller = new CastorMarshaller() {
    +			@Override
    +			protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource) {
    +				result.set(xmlReader);
    +				return null;
    +			}
    +		};
    +
    +		// 1. external-general-entities disabled (default)
    +
    +		marshaller.unmarshal(new StreamSource("1"));
    +		assertNotNull(result.get());
    +		assertEquals(false, result.get().getFeature("http://xml.org/sax/features/external-general-entities"));
    +
    +		// 2. external-general-entities disabled (default)
    +
    +		result.set(null);
    +		marshaller.setProcessExternalEntities(true);
    +		marshaller.unmarshal(new StreamSource("1"));
    +		assertNotNull(result.get());
    +		assertEquals(true, result.get().getFeature("http://xml.org/sax/features/external-general-entities"));
    +	}
    +
    +	@Test
    +	public void unmarshalSaxSourceExternalEntities() throws Exception {
    +
    +		final AtomicReference<XMLReader> result = new AtomicReference<XMLReader>();
    +		CastorMarshaller marshaller = new CastorMarshaller() {
    +			@Override
    +			protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource) {
    +				result.set(xmlReader);
    +				return null;
    +			}
    +		};
    +
    +		// 1. external-general-entities disabled (default)
    +
    +		marshaller.unmarshal(new SAXSource(new InputSource("1")));
    +		assertNotNull(result.get());
    +		assertEquals(false, result.get().getFeature("http://xml.org/sax/features/external-general-entities"));
    +
    +		// 2. external-general-entities disabled (default)
    +
    +		result.set(null);
    +		marshaller.setProcessExternalEntities(true);
    +		marshaller.unmarshal(new SAXSource(new InputSource("1")));
    +		assertNotNull(result.get());
    +		assertEquals(true, result.get().getFeature("http://xml.org/sax/features/external-general-entities"));
    +	}
    +
     }
    
  • spring-oxm/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTests.java+74 1 modified
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2002-2013 the original author or authors.
    + * Copyright 2002-2014 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.
    @@ -31,9 +31,12 @@
     import javax.xml.namespace.QName;
     import javax.xml.transform.Result;
     import javax.xml.transform.sax.SAXResult;
    +import javax.xml.transform.sax.SAXSource;
     import javax.xml.transform.stream.StreamResult;
    +import javax.xml.transform.stream.StreamSource;
     
     import org.junit.Test;
    +import org.mockito.ArgumentCaptor;
     import org.mockito.InOrder;
     import org.springframework.core.io.ClassPathResource;
     import org.springframework.core.io.Resource;
    @@ -49,6 +52,7 @@
     import org.springframework.util.ReflectionUtils;
     import org.xml.sax.Attributes;
     import org.xml.sax.ContentHandler;
    +import org.xml.sax.InputSource;
     import org.xml.sax.Locator;
     
     
    @@ -301,6 +305,75 @@ public void marshalAWrappedObjectHoldingAnXmlElementDeclElement() throws Excepti
     				writer.toString(), "<airplane><name>test</name></airplane>");
     	}
     
    +	// SPR-10806
    +
    +	@Test
    +	public void unmarshalStreamSourceExternalEntities() throws Exception {
    +
    +		final javax.xml.bind.Unmarshaller unmarshaller = mock(javax.xml.bind.Unmarshaller.class);
    +		Jaxb2Marshaller marshaller = new Jaxb2Marshaller() {
    +			@Override
    +			protected javax.xml.bind.Unmarshaller createUnmarshaller() {
    +				return unmarshaller;
    +			}
    +		};
    +
    +		// 1. external-general-entities disabled (default)
    +
    +		marshaller.unmarshal(new StreamSource("1"));
    +		ArgumentCaptor<SAXSource> sourceCaptor = ArgumentCaptor.forClass(SAXSource.class);
    +		verify(unmarshaller).unmarshal(sourceCaptor.capture());
    +
    +		SAXSource result = sourceCaptor.getValue();
    +		assertEquals(false, result.getXMLReader().getFeature("http://xml.org/sax/features/external-general-entities"));
    +
    +		// 2. external-general-entities enabled
    +
    +		reset(unmarshaller);
    +		marshaller.setProcessExternalEntities(true);
    +
    +		marshaller.unmarshal(new StreamSource("1"));
    +		verify(unmarshaller).unmarshal(sourceCaptor.capture());
    +
    +		result = sourceCaptor.getValue();
    +		assertEquals(true, result.getXMLReader().getFeature("http://xml.org/sax/features/external-general-entities"));
    +	}
    +
    +	// SPR-10806
    +
    +	@Test
    +	public void unmarshalSaxSourceExternalEntities() throws Exception {
    +
    +		final javax.xml.bind.Unmarshaller unmarshaller = mock(javax.xml.bind.Unmarshaller.class);
    +		Jaxb2Marshaller marshaller = new Jaxb2Marshaller() {
    +			@Override
    +			protected javax.xml.bind.Unmarshaller createUnmarshaller() {
    +				return unmarshaller;
    +			}
    +		};
    +
    +		// 1. external-general-entities disabled (default)
    +
    +		marshaller.unmarshal(new SAXSource(new InputSource("1")));
    +		ArgumentCaptor<SAXSource> sourceCaptor = ArgumentCaptor.forClass(SAXSource.class);
    +		verify(unmarshaller).unmarshal(sourceCaptor.capture());
    +
    +		SAXSource result = sourceCaptor.getValue();
    +		assertEquals(false, result.getXMLReader().getFeature("http://xml.org/sax/features/external-general-entities"));
    +
    +		// 2. external-general-entities enabled
    +
    +		reset(unmarshaller);
    +		marshaller.setProcessExternalEntities(true);
    +
    +		marshaller.unmarshal(new SAXSource(new InputSource("1")));
    +		verify(unmarshaller).unmarshal(sourceCaptor.capture());
    +
    +		result = sourceCaptor.getValue();
    +		assertEquals(true, result.getXMLReader().getFeature("http://xml.org/sax/features/external-general-entities"));
    +	}
    +
    +
     	@XmlRootElement
     	@SuppressWarnings("unused")
     	public static class DummyRootElement {
    
  • spring-oxm/src/test/java/org/springframework/oxm/jibx/JibxUnmarshallerTests.java+3 1 modified
    @@ -25,7 +25,9 @@
     import org.springframework.oxm.AbstractUnmarshallerTests;
     import org.springframework.oxm.Unmarshaller;
     
    -import static org.junit.Assert.*;
    +import static org.junit.Assert.assertEquals;
    +import static org.junit.Assert.assertNotNull;
    +
     
     /**
      * @author Arjen Poutsma
    
  • spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java+39 0 modified
    @@ -28,6 +28,8 @@
     import javax.xml.bind.annotation.XmlType;
     import javax.xml.transform.Result;
     import javax.xml.transform.Source;
    +import javax.xml.transform.sax.SAXSource;
    +import javax.xml.transform.stream.StreamSource;
     
     import org.springframework.core.annotation.AnnotationUtils;
     import org.springframework.http.HttpHeaders;
    @@ -36,6 +38,10 @@
     import org.springframework.http.converter.HttpMessageNotReadableException;
     import org.springframework.http.converter.HttpMessageNotWritableException;
     import org.springframework.util.ClassUtils;
    +import org.xml.sax.InputSource;
    +import org.xml.sax.SAXException;
    +import org.xml.sax.XMLReader;
    +import org.xml.sax.helpers.XMLReaderFactory;
     
     /**
      * Implementation of {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverter} that can read
    @@ -49,6 +55,18 @@
      */
     public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessageConverter<Object> {
     
    +		private boolean processExternalEntities = false;
    +
    +
    +	/**
    +	 * Indicates whether external XML entities are processed when converting to a Source.
    +	 * <p>Default is {@code false}, meaning that external entities are not resolved.
    +	 */
    +	public void setProcessExternalEntities(boolean processExternalEntities) {
    +		this.processExternalEntities = processExternalEntities;
    +	}
    +
    +
     	@Override
     	public boolean canRead(Class<?> clazz, MediaType mediaType) {
     		return (clazz.isAnnotationPresent(XmlRootElement.class) || clazz.isAnnotationPresent(XmlType.class)) &&
    @@ -69,6 +87,7 @@ protected boolean supports(Class<?> clazz) {
     	@Override
     	protected Object readFromSource(Class<?> clazz, HttpHeaders headers, Source source) throws IOException {
     		try {
    +			source = processSource(source);
     			Unmarshaller unmarshaller = createUnmarshaller(clazz);
     			if (clazz.isAnnotationPresent(XmlRootElement.class)) {
     				return unmarshaller.unmarshal(source);
    @@ -87,6 +106,26 @@ protected Object readFromSource(Class<?> clazz, HttpHeaders headers, Source sour
     		}
     	}
     
    +	protected Source processSource(Source source) {
    +		if (source instanceof StreamSource) {
    +			StreamSource streamSource = (StreamSource) source;
    +			InputSource inputSource = new InputSource(streamSource.getInputStream());
    +			try {
    +				XMLReader xmlReader = XMLReaderFactory.createXMLReader();
    +				String featureName = "http://xml.org/sax/features/external-general-entities";
    +				xmlReader.setFeature(featureName, this.processExternalEntities);
    +				return new SAXSource(xmlReader, inputSource);
    +			}
    +			catch (SAXException ex) {
    +				logger.warn("Processing of external entities could not be disabled", ex);
    +				return source;
    +			}
    +		}
    +		else {
    +			return source;
    +		}
    +	}
    +
     	@Override
     	protected void writeToResult(Object o, HttpHeaders headers, Result result) throws IOException {
     		try {
    
  • spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java+6 0 modified
    @@ -90,6 +90,12 @@ public void setProcessExternalEntities(boolean processExternalEntities) {
     		this.processExternalEntities = processExternalEntities;
     	}
     
    +	/**
    +	 * @return the configured value for whether XML external entities are allowed.
    +	 */
    +	public boolean isProcessExternalEntities() {
    +		return this.processExternalEntities;
    +	}
     
     	@Override
     	public boolean supports(Class<?> clazz) {
    
  • spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverterTest.java+32 0 modified
    @@ -32,6 +32,8 @@
     import org.springframework.aop.framework.AdvisedSupport;
     import org.springframework.aop.framework.AopProxy;
     import org.springframework.aop.framework.DefaultAopProxyFactory;
    +import org.springframework.core.io.ClassPathResource;
    +import org.springframework.core.io.Resource;
     import org.springframework.http.MediaType;
     import org.springframework.http.MockHttpInputMessage;
     import org.springframework.http.MockHttpOutputMessage;
    @@ -95,6 +97,33 @@ public void readXmlType() throws Exception {
     		assertEquals("Invalid result", "Hello World", result.s);
     	}
     
    +	@Test
    +	public void readXmlRootElementExternalEntityDisabled() throws Exception {
    +		Resource external = new ClassPathResource("external.txt", getClass());
    +		String content =  "<!DOCTYPE root [" +
    +				"  <!ELEMENT external ANY >\n" +
    +				"  <!ENTITY ext SYSTEM \"" + external.getURI() + "\" >]>" +
    +				"  <rootElement><external>&ext;</external></rootElement>";
    +		MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8"));
    +		RootElement rootElement = (RootElement) converter.read(RootElement.class, inputMessage);
    +
    +		assertEquals("", rootElement.external);
    +	}
    +
    +	@Test
    +	public void readXmlRootElementExternalEntityEnabled() throws Exception {
    +		Resource external = new ClassPathResource("external.txt", getClass());
    +		String content =  "<!DOCTYPE root [" +
    +				"  <!ELEMENT external ANY >\n" +
    +				"  <!ENTITY ext SYSTEM \"" + external.getURI() + "\" >]>" +
    +				"  <rootElement><external>&ext;</external></rootElement>";
    +		MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8"));
    +		this.converter.setProcessExternalEntities(true);
    +		RootElement rootElement = (RootElement) converter.read(RootElement.class, inputMessage);
    +
    +		assertEquals("Foo Bar", rootElement.external);
    +	}
    +
     	@Test
     	public void writeXmlRootElement() throws Exception {
     		MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
    @@ -121,6 +150,9 @@ public static class RootElement {
     		@XmlElement
     		public Type type = new Type();
     
    +		@XmlElement(required=false)
    +		public String external;
    +
     	}
     
     	@XmlType
    
edba32b30937

Add processExternalEntities support to OXM

https://github.com/spring-projects/spring-frameworkRossen StoyanchevFeb 18, 2014via ghsa
13 files changed · +349 26
  • spring-oxm/src/main/java/org/springframework/oxm/castor/CastorMarshaller.java+5 0 modified
    @@ -162,6 +162,11 @@ public void setEncoding(String encoding) {
     		this.encoding = encoding;
     	}
     
    +	@Override
    +	protected String getDefaultEncoding() {
    +		return this.encoding;
    +	}
    +
     	/**
     	 * Set the locations of the Castor XML mapping files.
     	 */
    
  • spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java+7 0 modified
    @@ -400,6 +400,13 @@ public void setProcessExternalEntities(boolean processExternalEntities) {
     		this.processExternalEntities = processExternalEntities;
     	}
     
    +	/**
    +	 * @return the configured value for whether XML external entities are allowed.
    +	 */
    +	public boolean isProcessExternalEntities() {
    +		return this.processExternalEntities;
    +	}
    +
     	@Override
     	public void setBeanClassLoader(ClassLoader classLoader) {
     		this.beanClassLoader = classLoader;
    
  • spring-oxm/src/main/java/org/springframework/oxm/jibx/JibxMarshaller.java+15 6 modified
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2002-2013 the original author or authors.
    + * Copyright 2002-2014 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.
    @@ -28,6 +28,7 @@
     import javax.xml.stream.XMLStreamException;
     import javax.xml.stream.XMLStreamReader;
     import javax.xml.stream.XMLStreamWriter;
    +import javax.xml.transform.OutputKeys;
     import javax.xml.transform.Result;
     import javax.xml.transform.Source;
     import javax.xml.transform.Transformer;
    @@ -149,6 +150,11 @@ public void setEncoding(String encoding) {
     		this.encoding = encoding;
     	}
     
    +	@Override
    +	protected String getDefaultEncoding() {
    +		return this.encoding;
    +	}
    +
     	/**
     	 * Set the document standalone flag for marshalling. By default, this flag is not present.
     	 */
    @@ -338,7 +344,7 @@ private void transformAndMarshal(Object graph, Result result) throws IOException
     		}
     		catch (TransformerException ex) {
     			throw new MarshallingFailureException(
    -					"Could not transform to [" + ClassUtils.getShortName(result.getClass()) + "]");
    +					"Could not transform to [" + ClassUtils.getShortName(result.getClass()) + "]", ex);
     		}
     
     	}
    @@ -398,7 +404,7 @@ protected Object unmarshalReader(Reader reader) throws XmlMappingException, IOEx
     	@Override
     	protected Object unmarshalDomNode(Node node) throws XmlMappingException {
     		try {
    -			return transformAndUnmarshal(new DOMSource(node));
    +			return transformAndUnmarshal(new DOMSource(node), null);
     		}
     		catch (IOException ex) {
     			throw new UnmarshallingFailureException("JiBX unmarshalling exception", ex);
    @@ -409,20 +415,23 @@ protected Object unmarshalDomNode(Node node) throws XmlMappingException {
     	protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource)
     			throws XmlMappingException, IOException {
     
    -		return transformAndUnmarshal(new SAXSource(xmlReader, inputSource));
    +		return transformAndUnmarshal(new SAXSource(xmlReader, inputSource), inputSource.getEncoding());
     	}
     
    -	private Object transformAndUnmarshal(Source source) throws IOException {
    +	private Object transformAndUnmarshal(Source source, String encoding) throws IOException {
     		try {
     			Transformer transformer = this.transformerFactory.newTransformer();
    +			if (encoding != null) {
    +				transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
    +			}
     			ByteArrayOutputStream os = new ByteArrayOutputStream();
     			transformer.transform(source, new StreamResult(os));
     			ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
     			return unmarshalInputStream(is);
     		}
     		catch (TransformerException ex) {
     			throw new MarshallingFailureException(
    -					"Could not transform from [" + ClassUtils.getShortName(source.getClass()) + "]");
    +					"Could not transform from [" + ClassUtils.getShortName(source.getClass()) + "]", ex);
     		}
     	}
     
    
  • spring-oxm/src/main/java/org/springframework/oxm/support/AbstractMarshaller.java+67 3 modified
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2002-2013 the original author or authors.
    + * Copyright 2002-2014 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.
    @@ -73,6 +73,34 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
     
     	private final Object documentBuilderFactoryMonitor = new Object();
     
    +	private boolean processExternalEntities = false;
    +
    +
    +	/**
    +	 * Indicates whether external XML entities are processed when unmarshalling.
    +	 * <p>Default is {@code false}, meaning that external entities are not resolved.
    +	 * Note that processing of external entities will only be enabled/disabled when the
    +	 * {@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.
    +	 */
    +	public void setProcessExternalEntities(boolean processExternalEntities) {
    +		this.processExternalEntities = processExternalEntities;
    +	}
    +
    +	/**
    +	 * @return the configured value for whether XML external entities are allowed.
    +	 */
    +	public boolean isProcessExternalEntities() {
    +		return this.processExternalEntities;
    +	}
    +
    +	/**
    +	 * @return the default encoding to use for marshalling or unmarshalling from
    +	 * 	a byte stream, or {@code null}.
    +	 */
    +	abstract protected String getDefaultEncoding();
    +
     
     	/**
     	 * Marshals the object graph with the given root into the provided {@code javax.xml.transform.Result}.
    @@ -133,7 +161,7 @@ else if (source instanceof SAXSource) {
     			return unmarshalSaxSource((SAXSource) source);
     		}
     		else if (source instanceof StreamSource) {
    -			return unmarshalStreamSource((StreamSource) source);
    +			return unmarshalStreamSourceNoExternalEntitities((StreamSource) source);
     		}
     		else {
     			throw new IllegalArgumentException("Unknown Source type: " + source.getClass());
    @@ -175,7 +203,9 @@ protected DocumentBuilderFactory createDocumentBuilderFactory() throws ParserCon
     	 * @throws SAXException if thrown by JAXP methods
     	 */
     	protected XMLReader createXmlReader() throws SAXException {
    -		return XMLReaderFactory.createXMLReader();
    +		XMLReader xmlReader = XMLReaderFactory.createXMLReader();
    +		xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", isProcessExternalEntities());
    +		return xmlReader;
     	}
     
     
    @@ -357,9 +387,43 @@ protected Object unmarshalSaxSource(SAXSource saxSource) throws XmlMappingExcept
     		return unmarshalSaxReader(saxSource.getXMLReader(), saxSource.getInputSource());
     	}
     
    +	/**
    +	 * Template method for handling {@code StreamSource}s with protection against
    +	 * the XML External Entity (XXE) processing vulnerability taking into account
    +	 * the value of the {@link #setProcessExternalEntities(boolean)} property.
    +	 * <p>
    +	 * The default implementation wraps the StreamSource as a SAXSource and delegates
    +	 * to {@link #unmarshalSaxSource(javax.xml.transform.sax.SAXSource)}.
    +	 *
    +	 * @param streamSource the {@code StreamSource}
    +	 * @return the object graph
    +	 * @throws IOException if an I/O exception occurs
    +	 * @throws XmlMappingException if the given source cannot be mapped to an object
    +	 *
    +	 * @see <a href="https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing">XML_External_Entity_(XXE)_Processing</a>
    +	 */
    +	protected Object unmarshalStreamSourceNoExternalEntitities(StreamSource streamSource) throws XmlMappingException, IOException {
    +		InputSource inputSource;
    +		if (streamSource.getInputStream() != null) {
    +			inputSource = new InputSource(streamSource.getInputStream());
    +			inputSource.setEncoding(getDefaultEncoding());
    +		}
    +		else if (streamSource.getReader() != null) {
    +			inputSource = new InputSource(streamSource.getReader());
    +		}
    +		else {
    +			inputSource = new InputSource(streamSource.getSystemId());
    +		}
    +		return unmarshalSaxSource(new SAXSource(inputSource));
    +	}
    +
     	/**
     	 * Template method for handling {@code StreamSource}s.
     	 * <p>This implementation defers to {@code unmarshalInputStream} or {@code unmarshalReader}.
    +	 * <p>As of 3.2.8 and 4.0.2 this method is no longer invoked from
    +	 * {@link #unmarshal(javax.xml.transform.Source)}. The method invoked instead is
    +	 * {@link #unmarshalStreamSourceNoExternalEntitities(javax.xml.transform.stream.StreamSource)}.
    +	 *
     	 * @param streamSource the {@code StreamSource}
     	 * @return the object graph
     	 * @throws IOException if an I/O exception occurs
    
  • spring-oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlBeansMarshaller.java+5 1 modified
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2002-2012 the original author or authors.
    + * Copyright 2002-2014 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.
    @@ -113,6 +113,10 @@ public boolean isValidating() {
     		return this.validating;
     	}
     
    +	@Override
    +	protected String getDefaultEncoding() {
    +		return null;
    +	}
     
     	/**
     	 * This implementation returns true if the given class is an implementation of {@link XmlObject}.
    
  • spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java+15 5 modified
    @@ -27,11 +27,9 @@
     import java.util.LinkedHashMap;
     import java.util.List;
     import java.util.Map;
    -import javax.xml.stream.XMLEventReader;
    -import javax.xml.stream.XMLEventWriter;
    -import javax.xml.stream.XMLStreamException;
    -import javax.xml.stream.XMLStreamReader;
    -import javax.xml.stream.XMLStreamWriter;
    +import javax.xml.stream.*;
    +import javax.xml.transform.stax.StAXSource;
    +import javax.xml.transform.stream.StreamSource;
     
     import com.thoughtworks.xstream.MarshallingStrategy;
     import com.thoughtworks.xstream.XStream;
    @@ -342,6 +340,11 @@ public void setEncoding(String encoding) {
     		this.encoding = encoding;
     	}
     
    +	@Override
    +	protected String getDefaultEncoding() {
    +		return this.encoding;
    +	}
    +
     	/**
     	 * Set the classes supported by this marshaller.
     	 * <p>If this property is empty (the default), all classes are supported.
    @@ -700,6 +703,13 @@ private void doMarshal(Object graph, HierarchicalStreamWriter streamWriter, Data
     
     	// Unmarshalling
     
    +	@Override
    +	protected Object unmarshalStreamSourceNoExternalEntitities(StreamSource streamSource)
    +			throws XmlMappingException, IOException {
    +
    +		return super.unmarshalStreamSource(streamSource);
    +	}
    +
     	@Override
     	protected Object unmarshalDomNode(Node node) throws XmlMappingException {
     		HierarchicalStreamReader streamReader;
    
  • spring-oxm/src/test/java/org/springframework/oxm/castor/CastorUnmarshallerTests.java+62 1 modified
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2002-2013 the original author or authors.
    + * Copyright 2002-2014 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.
    @@ -19,6 +19,8 @@
     import java.io.ByteArrayInputStream;
     import java.io.IOException;
     import java.io.StringReader;
    +import java.util.concurrent.atomic.AtomicReference;
    +import javax.xml.transform.sax.SAXSource;
     import javax.xml.transform.stream.StreamSource;
     
     import org.junit.Ignore;
    @@ -28,9 +30,13 @@
     import org.springframework.oxm.AbstractUnmarshallerTests;
     import org.springframework.oxm.MarshallingException;
     import org.springframework.oxm.Unmarshaller;
    +import org.xml.sax.InputSource;
    +import org.xml.sax.XMLReader;
     
    +import static junit.framework.Assert.assertNotNull;
     import static org.hamcrest.CoreMatchers.*;
     import static org.junit.Assert.*;
    +import static org.junit.Assert.assertEquals;
     
     /**
      * @author Arjen Poutsma
    @@ -203,4 +209,59 @@ private Object unmarshal(String xml) throws Exception {
     		StreamSource source = new StreamSource(new StringReader(xml));
     		return unmarshaller.unmarshal(source);
     	}
    +
    +	@Test
    +	public void unmarshalStreamSourceExternalEntities() throws Exception {
    +
    +		final AtomicReference<XMLReader> result = new AtomicReference<XMLReader>();
    +		CastorMarshaller marshaller = new CastorMarshaller() {
    +			@Override
    +			protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource) {
    +				result.set(xmlReader);
    +				return null;
    +			}
    +		};
    +
    +		// 1. external-general-entities disabled (default)
    +
    +		marshaller.unmarshal(new StreamSource("1"));
    +		assertNotNull(result.get());
    +		assertEquals(false, result.get().getFeature("http://xml.org/sax/features/external-general-entities"));
    +
    +		// 2. external-general-entities disabled (default)
    +
    +		result.set(null);
    +		marshaller.setProcessExternalEntities(true);
    +		marshaller.unmarshal(new StreamSource("1"));
    +		assertNotNull(result.get());
    +		assertEquals(true, result.get().getFeature("http://xml.org/sax/features/external-general-entities"));
    +	}
    +
    +	@Test
    +	public void unmarshalSaxSourceExternalEntities() throws Exception {
    +
    +		final AtomicReference<XMLReader> result = new AtomicReference<XMLReader>();
    +		CastorMarshaller marshaller = new CastorMarshaller() {
    +			@Override
    +			protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource) {
    +				result.set(xmlReader);
    +				return null;
    +			}
    +		};
    +
    +		// 1. external-general-entities disabled (default)
    +
    +		marshaller.unmarshal(new SAXSource(new InputSource("1")));
    +		assertNotNull(result.get());
    +		assertEquals(false, result.get().getFeature("http://xml.org/sax/features/external-general-entities"));
    +
    +		// 2. external-general-entities disabled (default)
    +
    +		result.set(null);
    +		marshaller.setProcessExternalEntities(true);
    +		marshaller.unmarshal(new SAXSource(new InputSource("1")));
    +		assertNotNull(result.get());
    +		assertEquals(true, result.get().getFeature("http://xml.org/sax/features/external-general-entities"));
    +	}
    +
     }
    
  • spring-oxm/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTests.java+75 5 modified
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2002-2013 the original author or authors.
    + * Copyright 2002-2014 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.
    @@ -31,9 +31,12 @@
     import javax.xml.namespace.QName;
     import javax.xml.transform.Result;
     import javax.xml.transform.sax.SAXResult;
    +import javax.xml.transform.sax.SAXSource;
     import javax.xml.transform.stream.StreamResult;
    +import javax.xml.transform.stream.StreamSource;
     
     import org.junit.Test;
    +import org.mockito.ArgumentCaptor;
     import org.mockito.InOrder;
     import org.springframework.core.io.ClassPathResource;
     import org.springframework.core.io.Resource;
    @@ -47,9 +50,7 @@
     import org.springframework.oxm.mime.MimeContainer;
     import org.springframework.util.FileCopyUtils;
     import org.springframework.util.ReflectionUtils;
    -import org.xml.sax.Attributes;
    -import org.xml.sax.ContentHandler;
    -import org.xml.sax.Locator;
    +import org.xml.sax.*;
     
     import static org.junit.Assert.*;
     import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
    @@ -289,7 +290,7 @@ public void marshalAttachments() throws Exception {
     	public void marshalAWrappedObjectHoldingAnXmlElementDeclElement() throws Exception {
     		// SPR-10714
     		marshaller = new Jaxb2Marshaller();
    -		marshaller.setPackagesToScan(new String[] { "org.springframework.oxm.jaxb" });
    +		marshaller.setPackagesToScan(new String[]{"org.springframework.oxm.jaxb"});
     		marshaller.afterPropertiesSet();
     		Airplane airplane = new Airplane();
     		airplane.setName("test");
    @@ -300,6 +301,75 @@ public void marshalAWrappedObjectHoldingAnXmlElementDeclElement() throws Excepti
     				writer.toString(), "<airplane><name>test</name></airplane>");
     	}
     
    +	// SPR-10806
    +
    +	@Test
    +	public void unmarshalStreamSourceExternalEntities() throws Exception {
    +
    +		final javax.xml.bind.Unmarshaller unmarshaller = mock(javax.xml.bind.Unmarshaller.class);
    +		Jaxb2Marshaller marshaller = new Jaxb2Marshaller() {
    +			@Override
    +			protected javax.xml.bind.Unmarshaller createUnmarshaller() {
    +				return unmarshaller;
    +			}
    +		};
    +
    +		// 1. external-general-entities disabled (default)
    +
    +		marshaller.unmarshal(new StreamSource("1"));
    +		ArgumentCaptor<SAXSource> sourceCaptor = ArgumentCaptor.forClass(SAXSource.class);
    +		verify(unmarshaller).unmarshal(sourceCaptor.capture());
    +
    +		SAXSource result = sourceCaptor.getValue();
    +		assertEquals(false, result.getXMLReader().getFeature("http://xml.org/sax/features/external-general-entities"));
    +
    +		// 2. external-general-entities enabled
    +
    +		reset(unmarshaller);
    +		marshaller.setProcessExternalEntities(true);
    +
    +		marshaller.unmarshal(new StreamSource("1"));
    +		verify(unmarshaller).unmarshal(sourceCaptor.capture());
    +
    +		result = sourceCaptor.getValue();
    +		assertEquals(true, result.getXMLReader().getFeature("http://xml.org/sax/features/external-general-entities"));
    +	}
    +
    +	// SPR-10806
    +
    +	@Test
    +	public void unmarshalSaxSourceExternalEntities() throws Exception {
    +
    +		final javax.xml.bind.Unmarshaller unmarshaller = mock(javax.xml.bind.Unmarshaller.class);
    +		Jaxb2Marshaller marshaller = new Jaxb2Marshaller() {
    +			@Override
    +			protected javax.xml.bind.Unmarshaller createUnmarshaller() {
    +				return unmarshaller;
    +			}
    +		};
    +
    +		// 1. external-general-entities disabled (default)
    +
    +		marshaller.unmarshal(new SAXSource(new InputSource("1")));
    +		ArgumentCaptor<SAXSource> sourceCaptor = ArgumentCaptor.forClass(SAXSource.class);
    +		verify(unmarshaller).unmarshal(sourceCaptor.capture());
    +
    +		SAXSource result = sourceCaptor.getValue();
    +		assertEquals(false, result.getXMLReader().getFeature("http://xml.org/sax/features/external-general-entities"));
    +
    +		// 2. external-general-entities enabled
    +
    +		reset(unmarshaller);
    +		marshaller.setProcessExternalEntities(true);
    +
    +		marshaller.unmarshal(new SAXSource(new InputSource("1")));
    +		verify(unmarshaller).unmarshal(sourceCaptor.capture());
    +
    +		result = sourceCaptor.getValue();
    +		assertEquals(true, result.getXMLReader().getFeature("http://xml.org/sax/features/external-general-entities"));
    +	}
    +
    +
     	@XmlRootElement
     	@SuppressWarnings("unused")
     	public static class DummyRootElement {
    
  • spring-oxm/src/test/java/org/springframework/oxm/jibx/JibxMarshallerTests.java+13 1 modified
    @@ -16,21 +16,34 @@
     
     package org.springframework.oxm.jibx;
     
    +import java.io.IOException;
     import java.io.StringWriter;
    +import java.util.concurrent.atomic.AtomicReference;
    +import javax.xml.transform.sax.SAXSource;
     import javax.xml.transform.stream.StreamResult;
    +import javax.xml.transform.stream.StreamSource;
     
     import org.custommonkey.xmlunit.XMLUnit;
     import org.junit.BeforeClass;
     import org.junit.Test;
     
    +import org.mockito.ArgumentCaptor;
     import org.springframework.oxm.AbstractMarshallerTests;
     import org.springframework.oxm.Marshaller;
    +import org.springframework.oxm.XmlMappingException;
    +import org.springframework.oxm.jaxb.Jaxb2Marshaller;
     import org.springframework.tests.Assume;
     import org.springframework.tests.TestGroup;
    +import org.xml.sax.InputSource;
    +import org.xml.sax.XMLReader;
     
     import static org.custommonkey.xmlunit.XMLAssert.*;
    +import static org.junit.Assert.assertEquals;
     import static org.junit.Assert.assertFalse;
     import static org.junit.Assert.assertTrue;
    +import static org.mockito.Mockito.mock;
    +import static org.mockito.Mockito.reset;
    +import static org.mockito.Mockito.verify;
     
     /**
      * @author Arjen Poutsma
    @@ -107,5 +120,4 @@ public void testSupports() throws Exception {
     		assertFalse("JibxMarshaller supports illegal type", marshaller.supports(getClass()));
     	}
     
    -
     }
    
  • spring-oxm/src/test/java/org/springframework/oxm/jibx/JibxUnmarshallerTests.java+3 1 modified
    @@ -28,7 +28,9 @@
     import org.springframework.tests.Assume;
     import org.springframework.tests.TestGroup;
     
    -import static org.junit.Assert.*;
    +import static org.junit.Assert.assertEquals;
    +import static org.junit.Assert.assertNotNull;
    +
     
     /**
      * @author Arjen Poutsma
    
  • spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java+41 1 modified
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2002-2010 the original author or authors.
    + * Copyright 2002-2014 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.
    @@ -28,6 +28,9 @@
     import javax.xml.bind.annotation.XmlType;
     import javax.xml.transform.Result;
     import javax.xml.transform.Source;
    +import javax.xml.transform.dom.DOMSource;
    +import javax.xml.transform.sax.SAXSource;
    +import javax.xml.transform.stream.StreamSource;
     
     import org.springframework.core.annotation.AnnotationUtils;
     import org.springframework.http.HttpHeaders;
    @@ -36,6 +39,11 @@
     import org.springframework.http.converter.HttpMessageNotReadableException;
     import org.springframework.http.converter.HttpMessageNotWritableException;
     import org.springframework.util.ClassUtils;
    +import org.springframework.util.xml.StaxUtils;
    +import org.xml.sax.InputSource;
    +import org.xml.sax.SAXException;
    +import org.xml.sax.XMLReader;
    +import org.xml.sax.helpers.XMLReaderFactory;
     
     /**
      * Implementation of {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverter} that can read
    @@ -49,6 +57,17 @@
      */
     public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessageConverter<Object> {
     
    +	private boolean processExternalEntities = false;
    +
    +
    +	/**
    +	 * Indicates whether external XML entities are processed when converting to a Source.
    +	 * <p>Default is {@code false}, meaning that external entities are not resolved.
    +	 */
    +	public void setProcessExternalEntities(boolean processExternalEntities) {
    +		this.processExternalEntities = processExternalEntities;
    +	}
    +
     	@Override
     	public boolean canRead(Class<?> clazz, MediaType mediaType) {
     		return (clazz.isAnnotationPresent(XmlRootElement.class) || clazz.isAnnotationPresent(XmlType.class)) &&
    @@ -69,6 +88,7 @@ protected boolean supports(Class<?> clazz) {
     	@Override
     	protected Object readFromSource(Class<?> clazz, HttpHeaders headers, Source source) throws IOException {
     		try {
    +			source = processSource(source);
     			Unmarshaller unmarshaller = createUnmarshaller(clazz);
     			if (clazz.isAnnotationPresent(XmlRootElement.class)) {
     				return unmarshaller.unmarshal(source);
    @@ -87,6 +107,26 @@ protected Object readFromSource(Class<?> clazz, HttpHeaders headers, Source sour
     		}
     	}
     
    +	protected Source processSource(Source source) {
    +		if (source instanceof StreamSource) {
    +			StreamSource streamSource = (StreamSource) source;
    +			InputSource inputSource = new InputSource(streamSource.getInputStream());
    +			try {
    +				XMLReader xmlReader = XMLReaderFactory.createXMLReader();
    +				String featureName = "http://xml.org/sax/features/external-general-entities";
    +				xmlReader.setFeature(featureName, this.processExternalEntities);
    +				return new SAXSource(xmlReader, inputSource);
    +			}
    +			catch (SAXException ex) {
    +				logger.warn("Processing of external entities could not be disabled", ex);
    +				return source;
    +			}
    +		}
    +		else {
    +			return source;
    +		}
    +	}
    +
     	@Override
     	protected void writeToResult(Object o, HttpHeaders headers, Result result) throws IOException {
     		try {
    
  • spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java+7 2 modified
    @@ -95,6 +95,12 @@ public void setProcessExternalEntities(boolean processExternalEntities) {
     		this.processExternalEntities = processExternalEntities;
     	}
     
    +	/**
    +	 * @return the configured value for whether XML external entities are allowed.
    +	 */
    +	public boolean isProcessExternalEntities() {
    +		return this.processExternalEntities;
    +	}
     
     	@Override
     	public boolean supports(Class<?> clazz) {
    @@ -159,8 +165,7 @@ private SAXSource readSAXSource(InputStream body) throws IOException {
     	private Source readStAXSource(InputStream body) {
     		try {
     			XMLInputFactory inputFactory = XMLInputFactory.newFactory();
    -			inputFactory.setProperty(
    -					"javax.xml.stream.isSupportingExternalEntities", this.processExternalEntities);
    +			inputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, this.processExternalEntities);
     			XMLStreamReader streamReader = inputFactory.createXMLStreamReader(body);
     			return new StAXSource(streamReader);
     		}
    
  • spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverterTests.java+34 0 modified
    @@ -32,9 +32,13 @@
     import org.springframework.aop.framework.AdvisedSupport;
     import org.springframework.aop.framework.AopProxy;
     import org.springframework.aop.framework.DefaultAopProxyFactory;
    +import org.springframework.core.io.ClassPathResource;
    +import org.springframework.core.io.Resource;
     import org.springframework.http.MediaType;
     import org.springframework.http.MockHttpInputMessage;
     import org.springframework.http.MockHttpOutputMessage;
    +import org.springframework.http.converter.HttpMessageNotReadableException;
    +import org.xml.sax.SAXParseException;
     
     /** @author Arjen Poutsma */
     public class Jaxb2RootElementHttpMessageConverterTests {
    @@ -95,6 +99,33 @@ public void readXmlType() throws Exception {
     		assertEquals("Invalid result", "Hello World", result.s);
     	}
     
    +	@Test
    +	public void readXmlRootElementExternalEntityDisabled() throws Exception {
    +		Resource external = new ClassPathResource("external.txt", getClass());
    +		String content =  "<!DOCTYPE root [" +
    +				"  <!ELEMENT external ANY >\n" +
    +				"  <!ENTITY ext SYSTEM \"" + external.getURI() + "\" >]>" +
    +				"  <rootElement><external>&ext;</external></rootElement>";
    +		MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8"));
    +		RootElement rootElement = (RootElement) converter.read(RootElement.class, inputMessage);
    +
    +		assertEquals("", rootElement.external);
    +	}
    +
    +	@Test
    +	public void readXmlRootElementExternalEntityEnabled() throws Exception {
    +		Resource external = new ClassPathResource("external.txt", getClass());
    +		String content =  "<!DOCTYPE root [" +
    +				"  <!ELEMENT external ANY >\n" +
    +				"  <!ENTITY ext SYSTEM \"" + external.getURI() + "\" >]>" +
    +				"  <rootElement><external>&ext;</external></rootElement>";
    +		MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8"));
    +		this.converter.setProcessExternalEntities(true);
    +		RootElement rootElement = (RootElement) converter.read(RootElement.class, inputMessage);
    +
    +		assertEquals("Foo Bar", rootElement.external);
    +	}
    +
     	@Test
     	public void writeXmlRootElement() throws Exception {
     		MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
    @@ -120,6 +151,9 @@ public static class RootElement {
     
     		private Type type = new Type();
     
    +		@XmlElement(required=false)
    +		public String external;
    +
     		public Type getType() {
     			return this.type;
     		}
    
1a8629d40825

Fix failing test

https://github.com/spring-projects/spring-frameworkRossen StoyanchevFeb 4, 2014via ghsa
2 files changed · +2 3
  • spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2CollectionHttpMessageConverter.java+1 1 modified
    @@ -229,7 +229,7 @@ protected void writeToResult(T t, HttpHeaders headers, Result result) throws IOE
     	 */
     	protected XMLInputFactory createXmlInputFactory() {
     		XMLInputFactory inputFactory = XMLInputFactory.newInstance();
    -		inputFactory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, false);
    +		inputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
     		return inputFactory;
     	}
     
    
  • spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2CollectionHttpMessageConverterTests.java+1 2 modified
    @@ -126,7 +126,7 @@ public void readXmlTypeSet() throws Exception {
     
     	@Test
     	@SuppressWarnings("unchecked")
    -	public void readXmlRootElementWithExternalEntity() throws Exception {
    +	public void readXmlRootElementExternalEntityDisabled() throws Exception {
     
     		Resource external = new ClassPathResource("external.txt", getClass());
     		String content =  "<!DOCTYPE root [" +
    @@ -151,7 +151,6 @@ public void readXmlRootElementExternalEntityEnabled() throws Exception {
     				"  <list><rootElement><type s=\"1\"/><external>&ext;</external></rootElement></list>";
     		MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8"));
     
    -		// Now read with
     		Jaxb2CollectionHttpMessageConverter<?> c = new Jaxb2CollectionHttpMessageConverter<Collection<Object>>() {
     			@Override
     			protected XMLInputFactory createXmlInputFactory() {
    
1c5cab2a4069

Add external entity test

https://github.com/spring-projects/spring-frameworkRossen StoyanchevFeb 3, 2014via ghsa
1 file changed · +49 3
  • spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2CollectionHttpMessageConverterTests.java+49 3 modified
    @@ -28,11 +28,15 @@
     import javax.xml.bind.annotation.XmlElement;
     import javax.xml.bind.annotation.XmlRootElement;
     import javax.xml.bind.annotation.XmlType;
    +import javax.xml.stream.XMLInputFactory;
     
     import org.junit.Before;
     import org.junit.Test;
     import org.springframework.core.ParameterizedTypeReference;
    +import org.springframework.core.io.ClassPathResource;
    +import org.springframework.core.io.Resource;
     import org.springframework.http.MockHttpInputMessage;
    +import org.springframework.http.converter.HttpMessageConverter;
     
     /**
      * Test fixture for {@link Jaxb2CollectionHttpMessageConverter}.
    @@ -120,6 +124,48 @@ public void readXmlTypeSet() throws Exception {
     		assertTrue("Invalid result", result.contains(new TestType("2")));
     	}
     
    +	@Test
    +	@SuppressWarnings("unchecked")
    +	public void readXmlRootElementWithExternalEntity() throws Exception {
    +
    +		Resource external = new ClassPathResource("external.txt", getClass());
    +		String content =  "<!DOCTYPE root [" +
    +				"  <!ELEMENT external ANY >\n" +
    +				"  <!ENTITY ext SYSTEM \"" + external.getURI() + "\" >]>" +
    +				"  <list><rootElement><type s=\"1\"/><external>&ext;</external></rootElement></list>";
    +		MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8"));
    +
    +		Collection<RootElement> result = converter.read(rootElementListType, null, inputMessage);
    +		assertEquals(1, result.size());
    +		assertEquals("", result.iterator().next().external);
    +	}
    +
    +	@Test
    +	@SuppressWarnings("unchecked")
    +	public void readXmlRootElementExternalEntityEnabled() throws Exception {
    +
    +		Resource external = new ClassPathResource("external.txt", getClass());
    +		String content =  "<!DOCTYPE root [" +
    +				"  <!ELEMENT external ANY >\n" +
    +				"  <!ENTITY ext SYSTEM \"" + external.getURI() + "\" >]>" +
    +				"  <list><rootElement><type s=\"1\"/><external>&ext;</external></rootElement></list>";
    +		MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8"));
    +
    +		// Now read with
    +		Jaxb2CollectionHttpMessageConverter<?> c = new Jaxb2CollectionHttpMessageConverter<Collection<Object>>() {
    +			@Override
    +			protected XMLInputFactory createXmlInputFactory() {
    +				XMLInputFactory inputFactory = XMLInputFactory.newInstance();
    +				inputFactory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, true);
    +				return inputFactory;
    +			}
    +		};
    +
    +		Collection<RootElement> result = c.read(rootElementListType, null, inputMessage);
    +		assertEquals(1, result.size());
    +		assertEquals("Foo Bar", result.iterator().next().external);
    +	}
    +
     
     	@XmlRootElement
     	public static class RootElement {
    @@ -134,6 +180,9 @@ public RootElement(String s) {
     		@XmlElement
     		public TestType type = new TestType();
     
    +		@XmlElement(required=false)
    +		public String external;
    +
     		@Override
     		public boolean equals(Object o) {
     			if (this == o) {
    @@ -181,9 +230,6 @@ public boolean equals(Object o) {
     		public int hashCode() {
     			return s.hashCode();
     		}
    -
    -
    -
     	}
     
     }
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

12

News mentions

0

No linked articles in our index yet.