Skip to content

Commit fb0683c

Browse files
rstoyanchevPhillip Webb
authored andcommitted
Add processExternalEntities support to OXM
Update OXM AbstractMarshaller to support processing of external XML entities. By default external entities will not be processed. Issue: SPR-11376
1 parent 09c5720 commit fb0683c

File tree

12 files changed

+327
-17
lines changed

12 files changed

+327
-17
lines changed

spring-oxm/src/main/java/org/springframework/oxm/castor/CastorMarshaller.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -162,6 +162,11 @@ public void setEncoding(String encoding) {
162162
this.encoding = encoding;
163163
}
164164

165+
@Override
166+
protected String getDefaultEncoding() {
167+
return this.encoding;
168+
}
169+
165170
/**
166171
* Set the locations of the Castor XML mapping files.
167172
*/
@@ -604,7 +609,7 @@ protected final Object unmarshalReader(Reader reader) throws XmlMappingException
604609
}
605610

606611
@Override
607-
protected final Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource)
612+
protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource)
608613
throws XmlMappingException, IOException {
609614

610615
UnmarshalHandler unmarshalHandler = createUnmarshaller().createHandler();

spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -401,6 +401,13 @@ public void setProcessExternalEntities(boolean processExternalEntities) {
401401
this.processExternalEntities = processExternalEntities;
402402
}
403403

404+
/**
405+
* @return the configured value for whether XML external entities are allowed.
406+
*/
407+
public boolean isProcessExternalEntities() {
408+
return this.processExternalEntities;
409+
}
410+
404411
@Override
405412
public void setBeanClassLoader(ClassLoader classLoader) {
406413
this.beanClassLoader = classLoader;

spring-oxm/src/main/java/org/springframework/oxm/jibx/JibxMarshaller.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@
2828
import javax.xml.stream.XMLStreamException;
2929
import javax.xml.stream.XMLStreamReader;
3030
import javax.xml.stream.XMLStreamWriter;
31+
import javax.xml.transform.OutputKeys;
3132
import javax.xml.transform.Result;
3233
import javax.xml.transform.Source;
3334
import javax.xml.transform.Transformer;
@@ -148,6 +149,11 @@ public void setEncoding(String encoding) {
148149
this.encoding = encoding;
149150
}
150151

152+
@Override
153+
protected String getDefaultEncoding() {
154+
return this.encoding;
155+
}
156+
151157
/**
152158
* Set the document standalone flag for marshalling. By default, this flag is not present.
153159
*/
@@ -389,13 +395,12 @@ protected Object unmarshalReader(Reader reader) throws XmlMappingException, IOEx
389395
}
390396
}
391397

392-
393398
// Unsupported Unmarshalling
394399

395400
@Override
396401
protected Object unmarshalDomNode(Node node) throws XmlMappingException {
397402
try {
398-
return transformAndUnmarshal(new DOMSource(node));
403+
return transformAndUnmarshal(new DOMSource(node), null);
399404
}
400405
catch (IOException ex) {
401406
throw new UnmarshallingFailureException("JiBX unmarshalling exception", ex);
@@ -406,20 +411,23 @@ protected Object unmarshalDomNode(Node node) throws XmlMappingException {
406411
protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource)
407412
throws XmlMappingException, IOException {
408413

409-
return transformAndUnmarshal(new SAXSource(xmlReader, inputSource));
414+
return transformAndUnmarshal(new SAXSource(xmlReader, inputSource), inputSource.getEncoding());
410415
}
411416

412-
private Object transformAndUnmarshal(Source source) throws IOException {
417+
private Object transformAndUnmarshal(Source source, String encoding) throws IOException {
413418
try {
414419
Transformer transformer = this.transformerFactory.newTransformer();
420+
if (encoding != null) {
421+
transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
422+
}
415423
ByteArrayOutputStream os = new ByteArrayOutputStream();
416424
transformer.transform(source, new StreamResult(os));
417425
ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
418426
return unmarshalInputStream(is);
419427
}
420428
catch (TransformerException ex) {
421429
throw new MarshallingFailureException(
422-
"Could not transform from [" + ClassUtils.getShortName(source.getClass()) + "]");
430+
"Could not transform from [" + ClassUtils.getShortName(source.getClass()) + "]", ex);
423431
}
424432
}
425433

spring-oxm/src/main/java/org/springframework/oxm/support/AbstractMarshaller.java

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -73,6 +73,33 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
7373

7474
private final Object documentBuilderFactoryMonitor = new Object();
7575

76+
private boolean processExternalEntities = false;
77+
78+
79+
/**
80+
* Indicates whether external XML entities are processed when unmarshalling.
81+
* <p>Default is {@code false}, meaning that external entities are not resolved.
82+
* Note that processing of external entities will only be enabled/disabled when the
83+
* {@code Source} passed to {@link #unmarshal(Source)} is a {@link SAXSource} or
84+
* {@link StreamSource}. It has no effect for {@link DOMSource} or {@link StAXSource}
85+
* instances.
86+
*/
87+
public void setProcessExternalEntities(boolean processExternalEntities) {
88+
this.processExternalEntities = processExternalEntities;
89+
}
90+
91+
/**
92+
* @return the configured value for whether XML external entities are allowed.
93+
*/
94+
public boolean isProcessExternalEntities() {
95+
return this.processExternalEntities;
96+
}
97+
98+
/**
99+
* @return the default encoding to use for marshalling or unmarshalling from
100+
* a byte stream, or {@code null}.
101+
*/
102+
abstract protected String getDefaultEncoding();
76103

77104
/**
78105
* 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) {
131158
return unmarshalSaxSource((SAXSource) source);
132159
}
133160
else if (source instanceof StreamSource) {
134-
return unmarshalStreamSource((StreamSource) source);
161+
return unmarshalStreamSourceNoExternalEntitities((StreamSource) source);
135162
}
136163
else {
137164
throw new IllegalArgumentException("Unknown Source type: " + source.getClass());
@@ -173,7 +200,9 @@ protected DocumentBuilderFactory createDocumentBuilderFactory() throws ParserCon
173200
* @throws SAXException if thrown by JAXP methods
174201
*/
175202
protected XMLReader createXmlReader() throws SAXException {
176-
return XMLReaderFactory.createXMLReader();
203+
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
204+
xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", isProcessExternalEntities());
205+
return xmlReader;
177206
}
178207

179208

@@ -355,9 +384,44 @@ protected Object unmarshalSaxSource(SAXSource saxSource) throws XmlMappingExcept
355384
return unmarshalSaxReader(saxSource.getXMLReader(), saxSource.getInputSource());
356385
}
357386

387+
/**
388+
* Template method for handling {@code StreamSource}s with protection against
389+
* the XML External Entity (XXE) processing vulnerability taking into account
390+
* the value of the {@link #setProcessExternalEntities(boolean)} property.
391+
* <p>
392+
* The default implementation wraps the StreamSource as a SAXSource and delegates
393+
* to {@link #unmarshalSaxSource(javax.xml.transform.sax.SAXSource)}.
394+
*
395+
* @param streamSource the {@code StreamSource}
396+
* @return the object graph
397+
* @throws IOException if an I/O exception occurs
398+
* @throws XmlMappingException if the given source cannot be mapped to an object
399+
*
400+
* @see <a href="https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing">XML_External_Entity_(XXE)_Processing</a>
401+
*/
402+
protected Object unmarshalStreamSourceNoExternalEntitities(StreamSource streamSource)
403+
throws XmlMappingException, IOException {
404+
405+
InputSource inputSource;
406+
if (streamSource.getInputStream() != null) {
407+
inputSource = new InputSource(streamSource.getInputStream());
408+
inputSource.setEncoding(getDefaultEncoding());
409+
}
410+
else if (streamSource.getReader() != null) {
411+
inputSource = new InputSource(streamSource.getReader());
412+
}
413+
else {
414+
inputSource = new InputSource(streamSource.getSystemId());
415+
}
416+
return unmarshalSaxSource(new SAXSource(inputSource));
417+
}
418+
358419
/**
359420
* Template method for handling {@code StreamSource}s.
360-
* <p>This implementation defers to {@code unmarshalInputStream} or {@code unmarshalReader}.
421+
* <p>As of 3.2.8 and 4.0.2 this method is no longer invoked from
422+
* {@link #unmarshal(javax.xml.transform.Source)}. The method invoked instead is
423+
* {@link #unmarshalStreamSourceNoExternalEntitities(javax.xml.transform.stream.StreamSource)}.
424+
*
361425
* @param streamSource the {@code StreamSource}
362426
* @return the object graph
363427
* @throws IOException if an I/O exception occurs

spring-oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlBeansMarshaller.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ public boolean isValidating() {
113113
return this.validating;
114114
}
115115

116+
@Override
117+
protected String getDefaultEncoding() {
118+
return null;
119+
}
116120

117121
/**
118122
* 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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@
3131
import javax.xml.stream.XMLStreamException;
3232
import javax.xml.stream.XMLStreamReader;
3333
import javax.xml.stream.XMLStreamWriter;
34+
import javax.xml.transform.stream.StreamSource;
3435

3536
import com.thoughtworks.xstream.XStream;
3637
import com.thoughtworks.xstream.converters.ConversionException;
@@ -353,6 +354,11 @@ public void setEncoding(String encoding) {
353354
this.encoding = encoding;
354355
}
355356

357+
@Override
358+
protected String getDefaultEncoding() {
359+
return this.encoding;
360+
}
361+
356362
/**
357363
* Set the classes supported by this marshaller.
358364
* <p>If this property is empty (the default), all classes are supported.
@@ -482,6 +488,11 @@ private void marshal(Object graph, HierarchicalStreamWriter streamWriter) {
482488

483489
// Unmarshalling
484490

491+
@Override
492+
protected Object unmarshalStreamSourceNoExternalEntitities(StreamSource streamSource) throws XmlMappingException, IOException {
493+
return super.unmarshalStreamSource(streamSource);
494+
}
495+
485496
@Override
486497
protected Object unmarshalDomNode(Node node) throws XmlMappingException {
487498
HierarchicalStreamReader streamReader;

spring-oxm/src/test/java/org/springframework/oxm/castor/CastorUnmarshallerTests.java

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,8 @@
1919
import java.io.ByteArrayInputStream;
2020
import java.io.IOException;
2121
import java.io.StringReader;
22+
import java.util.concurrent.atomic.AtomicReference;
23+
import javax.xml.transform.sax.SAXSource;
2224
import javax.xml.transform.stream.StreamSource;
2325

2426
import org.junit.Ignore;
@@ -28,6 +30,8 @@
2830
import org.springframework.oxm.AbstractUnmarshallerTests;
2931
import org.springframework.oxm.MarshallingException;
3032
import org.springframework.oxm.Unmarshaller;
33+
import org.xml.sax.InputSource;
34+
import org.xml.sax.XMLReader;
3135

3236
import static org.hamcrest.CoreMatchers.*;
3337
import static org.junit.Assert.*;
@@ -203,4 +207,59 @@ private Object unmarshal(String xml) throws Exception {
203207
StreamSource source = new StreamSource(new StringReader(xml));
204208
return unmarshaller.unmarshal(source);
205209
}
210+
211+
@Test
212+
public void unmarshalStreamSourceExternalEntities() throws Exception {
213+
214+
final AtomicReference<XMLReader> result = new AtomicReference<XMLReader>();
215+
CastorMarshaller marshaller = new CastorMarshaller() {
216+
@Override
217+
protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource) {
218+
result.set(xmlReader);
219+
return null;
220+
}
221+
};
222+
223+
// 1. external-general-entities disabled (default)
224+
225+
marshaller.unmarshal(new StreamSource("1"));
226+
assertNotNull(result.get());
227+
assertEquals(false, result.get().getFeature("http://xml.org/sax/features/external-general-entities"));
228+
229+
// 2. external-general-entities disabled (default)
230+
231+
result.set(null);
232+
marshaller.setProcessExternalEntities(true);
233+
marshaller.unmarshal(new StreamSource("1"));
234+
assertNotNull(result.get());
235+
assertEquals(true, result.get().getFeature("http://xml.org/sax/features/external-general-entities"));
236+
}
237+
238+
@Test
239+
public void unmarshalSaxSourceExternalEntities() throws Exception {
240+
241+
final AtomicReference<XMLReader> result = new AtomicReference<XMLReader>();
242+
CastorMarshaller marshaller = new CastorMarshaller() {
243+
@Override
244+
protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource) {
245+
result.set(xmlReader);
246+
return null;
247+
}
248+
};
249+
250+
// 1. external-general-entities disabled (default)
251+
252+
marshaller.unmarshal(new SAXSource(new InputSource("1")));
253+
assertNotNull(result.get());
254+
assertEquals(false, result.get().getFeature("http://xml.org/sax/features/external-general-entities"));
255+
256+
// 2. external-general-entities disabled (default)
257+
258+
result.set(null);
259+
marshaller.setProcessExternalEntities(true);
260+
marshaller.unmarshal(new SAXSource(new InputSource("1")));
261+
assertNotNull(result.get());
262+
assertEquals(true, result.get().getFeature("http://xml.org/sax/features/external-general-entities"));
263+
}
264+
206265
}

0 commit comments

Comments
 (0)