Skip to content

Commit edba32b

Browse files
rstoyanchevPhillip Webb
authored and
Phillip Webb
committed
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 7efd54e commit edba32b

File tree

13 files changed

+349
-26
lines changed

13 files changed

+349
-26
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,13 @@ public void setProcessExternalEntities(boolean processExternalEntities) {
400400
this.processExternalEntities = processExternalEntities;
401401
}
402402

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

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

Lines changed: 15 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;
@@ -149,6 +150,11 @@ public void setEncoding(String encoding) {
149150
this.encoding = encoding;
150151
}
151152

153+
@Override
154+
protected String getDefaultEncoding() {
155+
return this.encoding;
156+
}
157+
152158
/**
153159
* Set the document standalone flag for marshalling. By default, this flag is not present.
154160
*/
@@ -338,7 +344,7 @@ private void transformAndMarshal(Object graph, Result result) throws IOException
338344
}
339345
catch (TransformerException ex) {
340346
throw new MarshallingFailureException(
341-
"Could not transform to [" + ClassUtils.getShortName(result.getClass()) + "]");
347+
"Could not transform to [" + ClassUtils.getShortName(result.getClass()) + "]", ex);
342348
}
343349

344350
}
@@ -398,7 +404,7 @@ protected Object unmarshalReader(Reader reader) throws XmlMappingException, IOEx
398404
@Override
399405
protected Object unmarshalDomNode(Node node) throws XmlMappingException {
400406
try {
401-
return transformAndUnmarshal(new DOMSource(node));
407+
return transformAndUnmarshal(new DOMSource(node), null);
402408
}
403409
catch (IOException ex) {
404410
throw new UnmarshallingFailureException("JiBX unmarshalling exception", ex);
@@ -409,20 +415,23 @@ protected Object unmarshalDomNode(Node node) throws XmlMappingException {
409415
protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource)
410416
throws XmlMappingException, IOException {
411417

412-
return transformAndUnmarshal(new SAXSource(xmlReader, inputSource));
418+
return transformAndUnmarshal(new SAXSource(xmlReader, inputSource), inputSource.getEncoding());
413419
}
414420

415-
private Object transformAndUnmarshal(Source source) throws IOException {
421+
private Object transformAndUnmarshal(Source source, String encoding) throws IOException {
416422
try {
417423
Transformer transformer = this.transformerFactory.newTransformer();
424+
if (encoding != null) {
425+
transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
426+
}
418427
ByteArrayOutputStream os = new ByteArrayOutputStream();
419428
transformer.transform(source, new StreamResult(os));
420429
ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
421430
return unmarshalInputStream(is);
422431
}
423432
catch (TransformerException ex) {
424433
throw new MarshallingFailureException(
425-
"Could not transform from [" + ClassUtils.getShortName(source.getClass()) + "]");
434+
"Could not transform from [" + ClassUtils.getShortName(source.getClass()) + "]", ex);
426435
}
427436
}
428437

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

Lines changed: 67 additions & 3 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.
@@ -73,6 +73,34 @@ 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();
103+
76104

77105
/**
78106
* 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) {
133161
return unmarshalSaxSource((SAXSource) source);
134162
}
135163
else if (source instanceof StreamSource) {
136-
return unmarshalStreamSource((StreamSource) source);
164+
return unmarshalStreamSourceNoExternalEntitities((StreamSource) source);
137165
}
138166
else {
139167
throw new IllegalArgumentException("Unknown Source type: " + source.getClass());
@@ -175,7 +203,9 @@ protected DocumentBuilderFactory createDocumentBuilderFactory() throws ParserCon
175203
* @throws SAXException if thrown by JAXP methods
176204
*/
177205
protected XMLReader createXmlReader() throws SAXException {
178-
return XMLReaderFactory.createXMLReader();
206+
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
207+
xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", isProcessExternalEntities());
208+
return xmlReader;
179209
}
180210

181211

@@ -357,9 +387,43 @@ protected Object unmarshalSaxSource(SAXSource saxSource) throws XmlMappingExcept
357387
return unmarshalSaxReader(saxSource.getXMLReader(), saxSource.getInputSource());
358388
}
359389

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

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

Lines changed: 5 additions & 1 deletion
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.
@@ -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: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,9 @@
2727
import java.util.LinkedHashMap;
2828
import java.util.List;
2929
import java.util.Map;
30-
import javax.xml.stream.XMLEventReader;
31-
import javax.xml.stream.XMLEventWriter;
32-
import javax.xml.stream.XMLStreamException;
33-
import javax.xml.stream.XMLStreamReader;
34-
import javax.xml.stream.XMLStreamWriter;
30+
import javax.xml.stream.*;
31+
import javax.xml.transform.stax.StAXSource;
32+
import javax.xml.transform.stream.StreamSource;
3533

3634
import com.thoughtworks.xstream.MarshallingStrategy;
3735
import com.thoughtworks.xstream.XStream;
@@ -342,6 +340,11 @@ public void setEncoding(String encoding) {
342340
this.encoding = encoding;
343341
}
344342

343+
@Override
344+
protected String getDefaultEncoding() {
345+
return this.encoding;
346+
}
347+
345348
/**
346349
* Set the classes supported by this marshaller.
347350
* <p>If this property is empty (the default), all classes are supported.
@@ -700,6 +703,13 @@ private void doMarshal(Object graph, HierarchicalStreamWriter streamWriter, Data
700703

701704
// Unmarshalling
702705

706+
@Override
707+
protected Object unmarshalStreamSourceNoExternalEntitities(StreamSource streamSource)
708+
throws XmlMappingException, IOException {
709+
710+
return super.unmarshalStreamSource(streamSource);
711+
}
712+
703713
@Override
704714
protected Object unmarshalDomNode(Node node) throws XmlMappingException {
705715
HierarchicalStreamReader streamReader;

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

Lines changed: 62 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,9 +30,13 @@
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

36+
import static junit.framework.Assert.assertNotNull;
3237
import static org.hamcrest.CoreMatchers.*;
3338
import static org.junit.Assert.*;
39+
import static org.junit.Assert.assertEquals;
3440

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

0 commit comments

Comments
 (0)