Skip to content

Commit 3436bef

Browse files
Harden identity transforms and codegen DOM parsing
Add XMLUtils.newSecureTransformerFactory() and use it where JAX-WS and SAAJ copy a caller-supplied Source, since an identity transform parses a StreamSource or SAXSource with the transformer's own unhardened parser. The type-mapping and custom-schema loaders now use the secure DOM factory. The never-read SAX parser pool in XMLUtils becomes deprecated no-ops. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 82e5b88 commit 3436bef

8 files changed

Lines changed: 156 additions & 78 deletions

File tree

‎modules/codegen/src/org/apache/axis2/wsdl/codegen/extension/TypeMapperExtension.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.apache.axis2.wsdl.databinding.TypeMapper;
2626
import org.apache.axis2.wsdl.databinding.CTypeMapper;
2727
import org.apache.axis2.wsdl.i18n.CodegenMessages;
28+
import org.apache.axis2.util.XMLUtils;
2829
import org.w3c.dom.Document;
2930
import org.w3c.dom.Element;
3031
import org.w3c.dom.Node;
@@ -134,8 +135,7 @@ private Document buildDocument(CodeGenConfiguration configuration)
134135
throws CodeGenerationException {
135136
try {
136137
DocumentBuilderFactory documentBuilderFactory
137-
= DocumentBuilderFactory.newInstance();
138-
documentBuilderFactory.setNamespaceAware(true);
138+
= XMLUtils.newSecureDocumentBuilderFactory();
139139
DocumentBuilder documentBuilder =
140140
documentBuilderFactory.newDocumentBuilder();
141141
return documentBuilder.parse(configuration.getTypeMappingFile());

‎modules/jaxws/src/org/apache/axis2/datasource/SourceDataSource.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.apache.axiom.om.OMException;
2323
import org.apache.axiom.om.ds.AbstractPullOMDataSource;
2424
import org.apache.axiom.om.util.StAXUtils;
25+
import org.apache.axis2.util.XMLUtils;
2526
import org.apache.commons.logging.Log;
2627
import org.apache.commons.logging.LogFactory;
2728

@@ -30,7 +31,6 @@
3031
import javax.xml.transform.Result;
3132
import javax.xml.transform.Source;
3233
import javax.xml.transform.Transformer;
33-
import javax.xml.transform.TransformerFactory;
3434
import javax.xml.transform.stream.StreamResult;
3535
import javax.xml.transform.stream.StreamSource;
3636
import java.io.ByteArrayInputStream;
@@ -82,7 +82,7 @@ private byte[] getContent(String encoding) throws UnsupportedEncodingException {
8282
} else {
8383
ByteArrayOutputStream out = new ByteArrayOutputStream();
8484
Result result = new StreamResult(out);
85-
Transformer transformer = TransformerFactory.newInstance().newTransformer();
85+
Transformer transformer = XMLUtils.newSecureTransformerFactory().newTransformer();
8686
transformer.transform(data, result);
8787
bytes = out.toByteArray();
8888
}

‎modules/jaxws/src/org/apache/axis2/jaxws/handler/LogicalMessageImpl.java‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
import javax.xml.transform.Transformer;
4848
import javax.xml.transform.TransformerConfigurationException;
4949
import javax.xml.transform.TransformerException;
50-
import javax.xml.transform.TransformerFactory;
5150
import javax.xml.transform.TransformerFactoryConfigurationError;
5251
import javax.xml.transform.dom.DOMSource;
5352
import javax.xml.transform.stream.StreamResult;
@@ -192,7 +191,7 @@ private Payloads createPayloads(Object content) {
192191

193192
if (Source.class.isAssignableFrom(content.getClass())) {
194193
try {
195-
Transformer trans = TransformerFactory.newInstance().newTransformer();
194+
Transformer trans = XMLUtils.newSecureTransformerFactory().newTransformer();
196195

197196
// First we have to get the content out of the original
198197
// Source object so we can build the cache from there.

‎modules/jaxws/src/org/apache/axis2/jaxws/utility/ConvertUtils.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.apache.axis2.jaxws.ExceptionFactory;
2323
import org.apache.axis2.jaxws.i18n.Messages;
24+
import org.apache.axis2.util.XMLUtils;
2425
import org.apache.commons.logging.Log;
2526
import org.apache.commons.logging.LogFactory;
2627

@@ -29,7 +30,6 @@
2930
import javax.xml.transform.Result;
3031
import javax.xml.transform.Source;
3132
import javax.xml.transform.Transformer;
32-
import javax.xml.transform.TransformerFactory;
3333
import javax.xml.transform.stream.StreamResult;
3434
import javax.xml.transform.stream.StreamSource;
3535
import jakarta.xml.ws.WebServiceException;
@@ -221,7 +221,7 @@ public static Object convert(Object arg, Class destClass) throws WebServiceExcep
221221
}
222222
ByteArrayOutputStream out = new ByteArrayOutputStream();
223223
Result result = new StreamResult(out);
224-
Transformer transformer = TransformerFactory.newInstance().newTransformer();
224+
Transformer transformer = XMLUtils.newSecureTransformerFactory().newTransformer();
225225
transformer.transform((Source) arg, result);
226226
byte[] bytes = out.toByteArray();
227227
return bytes;

‎modules/kernel/src/org/apache/axis2/description/java2wsdl/DefaultSchemaGenerator.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.apache.axis2.jaxrs.JAXRSUtils;
3333
import org.apache.axis2.util.JavaUtils;
3434
import org.apache.axis2.wsdl.WSDLConstants;
35+
import org.apache.axis2.util.XMLUtils;
3536
import org.apache.commons.logging.Log;
3637
import org.apache.commons.logging.LogFactory;
3738
import org.apache.ws.commons.schema.XmlSchema;
@@ -228,8 +229,7 @@ public DefaultSchemaGenerator(ClassLoader loader, String className,
228229
private void loadCustomSchemaFile() {
229230
if (customSchemaLocation != null) {
230231
try {
231-
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
232-
documentBuilderFactory.setNamespaceAware(true);
232+
DocumentBuilderFactory documentBuilderFactory = XMLUtils.newSecureDocumentBuilderFactory();
233233
Document doc = documentBuilderFactory.newDocumentBuilder().parse(new File(customSchemaLocation));
234234
XmlSchema schema = xmlSchemaCollection.read(doc, null);
235235
schemaMap.put(schema.getTargetNamespace(), schema);

‎modules/kernel/src/org/apache/axis2/util/XMLUtils.java‎

Lines changed: 44 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,16 @@
3636
import org.xml.sax.InputSource;
3737
import org.xml.sax.SAXException;
3838
import org.xml.sax.SAXParseException;
39-
import org.xml.sax.XMLReader;
4039

40+
import javax.xml.XMLConstants;
4141
import javax.xml.namespace.QName;
4242
import javax.xml.parsers.DocumentBuilder;
4343
import javax.xml.parsers.DocumentBuilderFactory;
4444
import javax.xml.parsers.ParserConfigurationException;
4545
import javax.xml.parsers.SAXParser;
46-
import javax.xml.parsers.SAXParserFactory;
4746
import javax.xml.stream.XMLStreamException;
47+
import javax.xml.transform.TransformerConfigurationException;
48+
import javax.xml.transform.TransformerFactory;
4849
import java.io.ByteArrayInputStream;
4950
import java.io.ByteArrayOutputStream;
5051
import java.io.IOException;
@@ -56,65 +57,26 @@
5657
import java.net.ProtocolException;
5758
import java.net.URL;
5859
import java.net.URLConnection;
59-
import java.util.Stack;
6060

6161

6262
public class XMLUtils {
6363
public static final String charEncoding = "ISO-8859-1";
64-
private static final String saxParserFactoryProperty =
65-
"javax.xml.parsers.SAXParserFactory";
6664

6765
private static DocumentBuilderFactory dbf = getDOMFactory();
68-
private static SAXParserFactory saxFactory;
69-
private static Stack saxParsers = new Stack();
7066

7167
private static String empty = "";
7268
private static ByteArrayInputStream bais = new ByteArrayInputStream(empty.getBytes());
7369

74-
static {
75-
// Initialize SAX Parser factory defaults
76-
initSAXFactory(null, true, false);
77-
}
78-
7970
/**
80-
* Initializes the SAX parser factory.
71+
* Formerly configured a pooled SAX parser factory that nothing in Axis2 ever
72+
* handed out.
8173
*
82-
* @param factoryClassName The (optional) class name of the desired
83-
* SAXParserFactory implementation. Will be
84-
* assigned to the system property
85-
* <b>javax.xml.parsers.SAXParserFactory</b>
86-
* unless this property is already set.
87-
* If <code>null</code>, leaves current setting
88-
* alone.
89-
* @param namespaceAware true if we want a namespace-aware parser
90-
* @param validating true if we want a validating parser
74+
* @deprecated has no effect; use a hardened parser at the point of use instead
9175
*/
76+
@Deprecated
9277
public static void initSAXFactory(String factoryClassName,
9378
boolean namespaceAware,
9479
boolean validating) {
95-
if (factoryClassName != null) {
96-
try {
97-
saxFactory = (SAXParserFactory) Loader.loadClass(factoryClassName).
98-
newInstance();
99-
/*
100-
* Set the system property only if it is not already set to
101-
* avoid corrupting environments in which Axis is embedded.
102-
*/
103-
if (System.getProperty(saxParserFactoryProperty) == null) {
104-
System.setProperty(saxParserFactoryProperty,
105-
factoryClassName);
106-
}
107-
} catch (Exception e) {
108-
saxFactory = SAXParserFactory.newInstance();
109-
}
110-
} else {
111-
saxFactory = SAXParserFactory.newInstance();
112-
}
113-
saxFactory.setNamespaceAware(namespaceAware);
114-
saxFactory.setValidating(validating);
115-
116-
// Discard existing parsers
117-
saxParsers.clear();
11880
}
11981

12082
/**
@@ -142,6 +104,39 @@ public static DocumentBuilderFactory newSecureDocumentBuilderFactory()
142104
return dbf;
143105
}
144106

107+
/**
108+
* A TransformerFactory that refuses external DTDs, external entities and
109+
* external stylesheets.
110+
* <p>
111+
* Use this for any transform whose Source may come from outside Axis2. An
112+
* identity transform parses a StreamSource, or a SAXSource without its own
113+
* XMLReader, with the transformer's internal parser, which ignores every
114+
* parser hardening applied elsewhere. Implementations that do not recognise
115+
* a restriction keep the rest rather than fail, since the JAXP 1.5 access
116+
* attributes are optional for third-party processors.
117+
*
118+
* @return a TransformerFactory with secure processing and no external access
119+
*/
120+
public static TransformerFactory newSecureTransformerFactory() {
121+
TransformerFactory tf = TransformerFactory.newInstance();
122+
try {
123+
tf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
124+
} catch (TransformerConfigurationException e) {
125+
// Not supported by this processor; the access attributes still apply.
126+
}
127+
setAttributeIfSupported(tf, XMLConstants.ACCESS_EXTERNAL_DTD, "");
128+
setAttributeIfSupported(tf, XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
129+
return tf;
130+
}
131+
132+
private static void setAttributeIfSupported(TransformerFactory tf, String name, String value) {
133+
try {
134+
tf.setAttribute(name, value);
135+
} catch (IllegalArgumentException e) {
136+
// Not recognised by this processor.
137+
}
138+
}
139+
145140
private static DocumentBuilderFactory getDOMFactory() {
146141
DocumentBuilderFactory dbf;
147142
try {
@@ -164,31 +159,14 @@ private static DocumentBuilderFactory getDOMFactory() {
164159
return (dbf);
165160
}
166161

167-
private static boolean tryReset = true;
168-
169162
/**
170-
* Returns a SAX parser for reuse.
163+
* Formerly returned a parser to a pool that was never drawn from, so every
164+
* released parser was retained for the life of the class.
171165
*
172-
* @param parser A SAX parser that is available for reuse
166+
* @deprecated has no effect
173167
*/
168+
@Deprecated
174169
public static void releaseSAXParser(SAXParser parser) {
175-
if (!tryReset) {
176-
return;
177-
}
178-
179-
//Free up possible ref. held by past contenthandler.
180-
try {
181-
XMLReader xmlReader = parser.getXMLReader();
182-
if (null != xmlReader) {
183-
synchronized (XMLUtils.class) {
184-
saxParsers.push(parser);
185-
}
186-
} else {
187-
tryReset = false;
188-
}
189-
} catch (org.xml.sax.SAXException e) {
190-
tryReset = false;
191-
}
192170
}
193171

194172
/**
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.axis2.util;
20+
21+
import java.io.ByteArrayInputStream;
22+
import java.io.File;
23+
import java.io.FileOutputStream;
24+
import java.io.StringWriter;
25+
import java.nio.charset.StandardCharsets;
26+
27+
import javax.xml.transform.ErrorListener;
28+
import javax.xml.transform.TransformerException;
29+
import javax.xml.transform.stream.StreamResult;
30+
import javax.xml.transform.stream.StreamSource;
31+
32+
import junit.framework.TestCase;
33+
34+
/**
35+
* An identity transform of a StreamSource parses it with the transformer's own
36+
* parser, so the factory is the only place an external entity can be refused.
37+
*/
38+
public class XMLUtilsSecureTransformerTest extends TestCase {
39+
40+
private static final String MARKER = "axis2-secret-marker";
41+
42+
private File secret;
43+
44+
@Override
45+
protected void setUp() throws Exception {
46+
secret = File.createTempFile("axis2-secure-transformer", ".txt");
47+
FileOutputStream out = new FileOutputStream(secret);
48+
try {
49+
out.write(MARKER.getBytes(StandardCharsets.UTF_8));
50+
} finally {
51+
out.close();
52+
}
53+
}
54+
55+
@Override
56+
protected void tearDown() throws Exception {
57+
secret.delete();
58+
}
59+
60+
private static StreamSource source(String xml) {
61+
return new StreamSource(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)));
62+
}
63+
64+
private static String transform(StreamSource source) throws TransformerException {
65+
javax.xml.transform.Transformer transformer =
66+
XMLUtils.newSecureTransformerFactory().newTransformer();
67+
// Keep the expected refusal out of the build log.
68+
transformer.setErrorListener(new ErrorListener() {
69+
public void warning(TransformerException e) {
70+
}
71+
72+
public void error(TransformerException e) throws TransformerException {
73+
throw e;
74+
}
75+
76+
public void fatalError(TransformerException e) throws TransformerException {
77+
throw e;
78+
}
79+
});
80+
StringWriter out = new StringWriter();
81+
transformer.transform(source, new StreamResult(out));
82+
return out.toString();
83+
}
84+
85+
public void testPlainDocumentIsCopied() throws Exception {
86+
String out = transform(source("<a xmlns='urn:test'><b>text</b></a>"));
87+
assertTrue(out, out.contains("<b>text</b>"));
88+
}
89+
90+
public void testExternalEntityIsNotResolved() throws Exception {
91+
String xml = "<?xml version='1.0'?>"
92+
+ "<!DOCTYPE a [<!ENTITY xxe SYSTEM '" + secret.toURI() + "'>]>"
93+
+ "<a>&xxe;</a>";
94+
try {
95+
String out = transform(source(xml));
96+
assertFalse("external entity was resolved: " + out, out.contains(MARKER));
97+
} catch (TransformerException expected) {
98+
// Refusing the document outright is equally acceptable.
99+
}
100+
}
101+
}

‎modules/saaj/src/org/apache/axis2/saaj/SOAPPartImpl.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.apache.axis2.saaj.util.IDGenerator;
3131
import org.apache.axis2.saaj.util.SAAJUtil;
3232
import org.apache.axis2.kernel.http.HTTPConstants;
33+
import org.apache.axis2.util.XMLUtils;
3334
import org.apache.commons.logging.Log;
3435
import org.apache.commons.logging.LogFactory;
3536
import org.w3c.dom.Attr;
@@ -61,7 +62,6 @@
6162
import javax.xml.transform.Result;
6263
import javax.xml.transform.Source;
6364
import javax.xml.transform.Transformer;
64-
import javax.xml.transform.TransformerFactory;
6565
import javax.xml.transform.TransformerFactoryConfigurationError;
6666
import javax.xml.transform.dom.DOMSource;
6767
import javax.xml.transform.stream.StreamResult;
@@ -331,7 +331,7 @@ public void setContent(Source source) throws SOAPException {
331331
reader = inputFactory.createXMLStreamReader(source);
332332
} else {
333333
Result result = new StreamResult(baos);
334-
Transformer xformer = TransformerFactory.newInstance().newTransformer();
334+
Transformer xformer = XMLUtils.newSecureTransformerFactory().newTransformer();
335335
xformer.transform(source, result);
336336
InputStream is = new ByteArrayInputStream(baos.toByteArray());
337337
reader = inputFactory.createXMLStreamReader(is);

0 commit comments

Comments
 (0)