Skip to content

Commit 5a7dec7

Browse files
Test the hardened transform and DOM call sites
Cover SAAJ setContent with a SAXSource, ConvertUtils and SourceDataSource copying a Reader- or SAX-backed Source, and the codegen type-mapping loader. Each test fails against the previous unhardened call site. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 3436bef commit 5a7dec7

4 files changed

Lines changed: 252 additions & 0 deletions

File tree

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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+
20+
package org.apache.axis2.wsdl.codegen.extension;
21+
22+
import org.apache.axis2.wsdl.codegen.CodeGenConfiguration;
23+
import org.apache.axis2.wsdl.codegen.CodeGenerationException;
24+
import org.junit.jupiter.api.Test;
25+
import org.junit.jupiter.api.io.TempDir;
26+
27+
import javax.xml.namespace.QName;
28+
import java.io.File;
29+
import java.nio.charset.StandardCharsets;
30+
import java.nio.file.Files;
31+
import java.nio.file.Path;
32+
33+
import static org.junit.jupiter.api.Assertions.assertEquals;
34+
import static org.junit.jupiter.api.Assertions.assertThrows;
35+
36+
/**
37+
* The type-mapping file is read with the secure DOM factory, so a DOCTYPE is
38+
* refused rather than its external entities resolved.
39+
*/
40+
public class TypeMapperExtensionTest {
41+
42+
private static final String MAPPING =
43+
"<mappings dbf='adb'><mapping>"
44+
+ "<qname namespace='urn:test'>Thing</qname>"
45+
+ "<value>%s</value>"
46+
+ "</mapping></mappings>";
47+
48+
@TempDir
49+
Path dir;
50+
51+
private CodeGenConfiguration configure(String mappingFile) throws Exception {
52+
File file = dir.resolve("mapping.xml").toFile();
53+
Files.write(file.toPath(), mappingFile.getBytes(StandardCharsets.UTF_8));
54+
CodeGenConfiguration configuration = new CodeGenConfiguration();
55+
configuration.setDatabindingType("adb");
56+
configuration.setTypeMappingFile(file);
57+
return configuration;
58+
}
59+
60+
@Test
61+
public void testMappingFileIsRead() throws Exception {
62+
CodeGenConfiguration configuration =
63+
configure(String.format(MAPPING, "com.example.Thing"));
64+
new TypeMapperExtension().engage(configuration);
65+
assertEquals("com.example.Thing", configuration.getTypeMapper()
66+
.getAllMappedNames().get(new QName("urn:test", "Thing")));
67+
}
68+
69+
@Test
70+
public void testMappingFileWithDoctypeIsRefused() throws Exception {
71+
Path secret = dir.resolve("secret.txt");
72+
Files.write(secret, "com.example.Secret".getBytes(StandardCharsets.UTF_8));
73+
String mapping = "<!DOCTYPE mappings [<!ENTITY xxe SYSTEM '" + secret.toUri() + "'>]>"
74+
+ String.format(MAPPING, "&xxe;");
75+
CodeGenConfiguration configuration = configure(mapping);
76+
assertThrows(CodeGenerationException.class,
77+
() -> new TypeMapperExtension().engage(configuration));
78+
}
79+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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+
20+
package org.apache.axis2.datasource;
21+
22+
import java.io.File;
23+
import java.io.FileWriter;
24+
import java.io.StringReader;
25+
26+
import javax.xml.stream.XMLStreamReader;
27+
import javax.xml.transform.sax.SAXSource;
28+
29+
import junit.framework.TestCase;
30+
31+
import org.apache.axiom.om.OMException;
32+
import org.xml.sax.InputSource;
33+
34+
/**
35+
* A SAXSource without its own XMLReader is copied through a transformer, which
36+
* parses it with its own parser.
37+
*/
38+
public class SourceDataSourceTests extends TestCase {
39+
40+
private static final String MARKER = "JAXWS_XXE_SECRET_MARKER";
41+
42+
private static String text(SAXSource source) throws Exception {
43+
XMLStreamReader reader = new SourceDataSource(source).getReader();
44+
StringBuilder text = new StringBuilder();
45+
while (reader.hasNext()) {
46+
if (reader.next() == XMLStreamReader.CHARACTERS) {
47+
text.append(reader.getText());
48+
}
49+
}
50+
return text.toString();
51+
}
52+
53+
public void testExternalEntityIsNotResolved() throws Exception {
54+
File secret = File.createTempFile("jaxws-xxe", ".txt");
55+
secret.deleteOnExit();
56+
try (FileWriter w = new FileWriter(secret)) {
57+
w.write(MARKER);
58+
}
59+
String xml = "<?xml version='1.0'?>"
60+
+ "<!DOCTYPE a [<!ENTITY xxe SYSTEM '" + secret.toURI() + "'>]>"
61+
+ "<a>&xxe;</a>";
62+
try {
63+
String out = text(new SAXSource(new InputSource(new StringReader(xml))));
64+
assertFalse("external entity was resolved: " + out, out.contains(MARKER));
65+
} catch (OMException expected) {
66+
// Refusing the document outright is equally acceptable.
67+
}
68+
}
69+
70+
public void testPlainDocumentIsRead() throws Exception {
71+
String out = text(new SAXSource(new InputSource(new StringReader("<a><b>text</b></a>"))));
72+
assertEquals("text", out);
73+
}
74+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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+
20+
package org.apache.axis2.jaxws.utility;
21+
22+
import java.io.File;
23+
import java.io.FileWriter;
24+
import java.io.StringReader;
25+
import java.nio.charset.StandardCharsets;
26+
27+
import javax.xml.transform.stream.StreamSource;
28+
29+
import jakarta.xml.ws.WebServiceException;
30+
import junit.framework.TestCase;
31+
32+
/**
33+
* A StreamSource backed by a Reader has no InputStream to copy, so convert()
34+
* copies it through a transformer, which parses it with its own parser.
35+
*/
36+
public class ConvertUtilsTests extends TestCase {
37+
38+
private static final String MARKER = "JAXWS_XXE_SECRET_MARKER";
39+
40+
public void testSourceToBytesDoesNotResolveExternalEntity() throws Exception {
41+
File secret = File.createTempFile("jaxws-xxe", ".txt");
42+
secret.deleteOnExit();
43+
try (FileWriter w = new FileWriter(secret)) {
44+
w.write(MARKER);
45+
}
46+
String xml = "<?xml version='1.0'?>"
47+
+ "<!DOCTYPE a [<!ENTITY xxe SYSTEM '" + secret.toURI() + "'>]>"
48+
+ "<a>&xxe;</a>";
49+
try {
50+
byte[] bytes = (byte[]) ConvertUtils.convert(
51+
new StreamSource(new StringReader(xml)), byte[].class);
52+
String out = new String(bytes, StandardCharsets.UTF_8);
53+
assertFalse("external entity was resolved: " + out, out.contains(MARKER));
54+
} catch (WebServiceException expected) {
55+
// Refusing the document outright is equally acceptable.
56+
}
57+
}
58+
59+
public void testSourceToBytesCopiesPlainDocument() throws Exception {
60+
byte[] bytes = (byte[]) ConvertUtils.convert(
61+
new StreamSource(new StringReader("<a><b>text</b></a>")), byte[].class);
62+
String out = new String(bytes, StandardCharsets.UTF_8);
63+
assertTrue(out, out.contains("<b>text</b>"));
64+
}
65+
}

‎modules/saaj/test/org/apache/axis2/saaj/SOAPPartTest.java‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.w3c.dom.Document;
2626
import org.w3c.dom.Node;
2727
import org.w3c.dom.NodeList;
28+
import org.xml.sax.InputSource;
2829

2930
import javax.xml.parsers.DocumentBuilder;
3031
import javax.xml.parsers.DocumentBuilderFactory;
@@ -40,6 +41,7 @@
4041
import jakarta.xml.soap.SOAPPart;
4142
import jakarta.xml.soap.Text;
4243
import javax.xml.transform.dom.DOMSource;
44+
import javax.xml.transform.sax.SAXSource;
4345
import javax.xml.transform.stream.StreamSource;
4446
import java.io.ByteArrayOutputStream;
4547
import java.io.File;
@@ -116,6 +118,38 @@ public void testSetContentRejectsExternalEntity() throws Exception {
116118
out.toString("UTF-8").contains(marker));
117119
}
118120

121+
/**
122+
* A SAXSource without its own XMLReader is parsed by the transformer that
123+
* setContent copies it through, not by the hardened StAX factory.
124+
*/
125+
@Test
126+
public void testSetContentSAXSourceRejectsExternalEntity() throws Exception {
127+
File secret = File.createTempFile("saaj-xxe", ".txt");
128+
secret.deleteOnExit();
129+
String marker = "SAAJ_XXE_SECRET_MARKER";
130+
try (FileWriter w = new FileWriter(secret)) {
131+
w.write(marker);
132+
}
133+
String xml =
134+
"<?xml version=\"1.0\"?>\n" +
135+
"<!DOCTYPE x [ <!ENTITY xxe SYSTEM \"" + secret.toURI() + "\"> ]>\n" +
136+
"<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
137+
"<soapenv:Body><item>&xxe;</item></soapenv:Body></soapenv:Envelope>";
138+
139+
SOAPMessage message = MessageFactory.newInstance().createMessage();
140+
SOAPPart soapPart = message.getSOAPPart();
141+
try {
142+
soapPart.setContent(new SAXSource(new InputSource(new StringReader(xml))));
143+
message.saveChanges();
144+
} catch (SOAPException expected) {
145+
return;
146+
}
147+
ByteArrayOutputStream out = new ByteArrayOutputStream();
148+
message.writeTo(out);
149+
assertFalse("external entity must not be expanded into the message",
150+
out.toString("UTF-8").contains(marker));
151+
}
152+
119153
public void getContents(Iterator iterator, String indent) {
120154
while (iterator.hasNext()) {
121155
Node node = (Node)iterator.next();

0 commit comments

Comments
 (0)