1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.xml.secure;
19
20 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
21
22 import java.io.StringReader;
23 import java.util.concurrent.atomic.AtomicBoolean;
24
25 import javax.xml.parsers.DocumentBuilderFactory;
26 import javax.xml.parsers.SAXParserFactory;
27 import javax.xml.transform.Source;
28 import javax.xml.validation.Validator;
29
30 import org.junit.jupiter.api.Assumptions;
31 import org.junit.jupiter.api.Tag;
32 import org.junit.jupiter.api.Test;
33 import org.xml.sax.InputSource;
34 import org.xml.sax.XMLReader;
35 import org.xml.sax.helpers.DefaultHandler;
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63 class ExternalParameterEntityTest {
64
65
66
67
68 private static final String DOM_PARAMETER_ENTITY_PROBE =
69 "<?xml version=\"1.0\"?>\n"
70 + "<!DOCTYPE root [\n"
71 + " <!ENTITY % p \"<!ENTITY child 'A'>\">\n"
72 + " %p;\n"
73 + "]>\n"
74 + "<root>&child;</root>";
75
76
77
78
79
80
81
82
83 private static final boolean DOM_ACCEPTS_PARAMETER_ENTITIES = probeDomAcceptsParameterEntities();
84
85 private static final String INSERTION = "&leaked;";
86
87
88
89
90
91
92 private static final String SAX_PARAMETER_ENTITY_PROBE =
93 "<?xml version=\"1.0\"?>\n"
94 + "<!DOCTYPE root [\n"
95 + " <!ENTITY % p SYSTEM \"about:invalid\">\n"
96 + " %p;\n"
97 + "]>\n"
98 + "<root/>";
99
100
101
102
103
104
105 private static final boolean SAX_RESOLVES_PARAMETER_ENTITIES = probeSaxResolvesParameterEntities();
106
107 private static boolean probeDomAcceptsParameterEntities() {
108 try {
109 DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(AttackTestSupport.inputSource(DOM_PARAMETER_ENTITY_PROBE));
110 return true;
111 } catch (final Exception e) {
112 return false;
113 }
114 }
115
116 private static boolean probeSaxResolvesParameterEntities() {
117 final AtomicBoolean called = new AtomicBoolean();
118 assertDoesNotThrow(() -> {
119 final XMLReader reader = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
120 reader.setEntityResolver((publicId, systemId) -> {
121 if ("about:invalid".equals(systemId)) {
122 called.set(true);
123 }
124 return new InputSource(new StringReader(""));
125 });
126 reader.setContentHandler(new DefaultHandler());
127 reader.setErrorHandler(new DefaultHandler());
128 reader.parse(AttackTestSupport.inputSource(SAX_PARAMETER_ENTITY_PROBE));
129 });
130 return called.get();
131 }
132
133 private static String withDoctype(final String rootQName, final String body) {
134 return "<?xml version=\"1.0\"?>\n"
135 + "<!DOCTYPE " + rootQName + " [\n"
136 + " <!ENTITY % xxe SYSTEM \"" + AttackTestSupport.resourceUrl("referenced.dtd") + "\">\n"
137 + " %xxe;\n"
138 + "]>\n"
139 + body + "\n";
140 }
141
142 private static String xmlPayload() {
143 return withDoctype("root", AttackTestSupport.xmlBody(INSERTION));
144 }
145
146 private static String xsdPayload() {
147 return withDoctype("xs:schema", AttackTestSupport.xsdBody(INSERTION));
148 }
149
150 private static String xsltPayload() {
151 return withDoctype("xsl:stylesheet", AttackTestSupport.xsltBody(INSERTION));
152 }
153
154 @Test
155 @Tag("dom")
156 void secureDomBlocksOrDoesNotLeak() {
157 Assumptions.assumeTrue(DOM_ACCEPTS_PARAMETER_ENTITIES,
158 "Skipped: platform DOM does not accept parameter entities");
159 AttackTestSupport.assertDomBlocksOrDoesNotLeak(xmlPayload());
160 }
161
162 @Test
163 @Tag("sax")
164 void secureSaxBlocksOrDoesNotLeak() {
165 Assumptions.assumeTrue(SAX_RESOLVES_PARAMETER_ENTITIES,
166 "Skipped: platform SAX parser does not invoke the entity resolver for parameter entities");
167 AttackTestSupport.assertSaxBlocksOrDoesNotLeak(xmlPayload());
168 }
169
170 @Test
171 @Tag("schema")
172 void secureSchemaBlocksOrDoesNotLeak() {
173 Assumptions.assumeTrue(SAX_RESOLVES_PARAMETER_ENTITIES,
174 "Skipped: platform SAX parser does not invoke the entity resolver for parameter entities");
175 AttackTestSupport.assertSchemaBlocksOrDoesNotLeak(AttackTestSupport.streamSource(xsdPayload()));
176 }
177
178 @Test
179 @Tag("stax")
180 void secureStaxBlocksOrDoesNotLeak() {
181 AttackTestSupport.assertStaxBlocksOrDoesNotLeak(xmlPayload());
182 }
183
184 @Test
185 @Tag("trax")
186 void secureTemplatesBlocksOrDoesNotLeak() {
187 Assumptions.assumeTrue(SAX_RESOLVES_PARAMETER_ENTITIES,
188 "Skipped: platform SAX parser does not invoke the entity resolver for parameter entities");
189 AttackTestSupport.assertTemplatesBlocksOrDoesNotLeak(AttackTestSupport.streamSource(xsltPayload()));
190 }
191
192 @Test
193 @Tag("trax")
194 void secureTransformerBlocksOrDoesNotLeak() {
195 Assumptions.assumeTrue(SAX_RESOLVES_PARAMETER_ENTITIES,
196 "Skipped: platform SAX parser does not invoke the entity resolver for parameter entities");
197 AttackTestSupport.assertTransformerBlocksOrDoesNotLeak(xmlPayload());
198 }
199
200 @Test
201 @Tag("schema")
202 void secureValidatorBlocksOrDoesNotLeak() {
203 Assumptions.assumeTrue(SAX_RESOLVES_PARAMETER_ENTITIES,
204 "Skipped: platform SAX parser does not invoke the entity resolver for parameter entities");
205 AttackTestSupport.assertValidatorBlocksOrDoesNotLeak(xmlPayload());
206 }
207
208 @Test
209 @Tag("sax")
210 void secureXmlReaderBlocksOrDoesNotLeak() {
211 Assumptions.assumeTrue(SAX_RESOLVES_PARAMETER_ENTITIES,
212 "Skipped: platform SAX parser does not invoke the entity resolver for parameter entities");
213 AttackTestSupport.assertXmlReaderBlocksOrDoesNotLeak(xmlPayload());
214 }
215
216 @Test
217 @Tag("dom")
218 void unconfiguredDomParses() {
219 Assumptions.assumeTrue(DOM_ACCEPTS_PARAMETER_ENTITIES,
220 "Skipped: platform DOM does not accept parameter entities");
221 AttackTestSupport.assertPermissiveDomParses(xmlPayload());
222 }
223
224 @Test
225 @Tag("sax")
226 void unconfiguredSaxParses() {
227 AttackTestSupport.assertPermissiveSaxParses(xmlPayload());
228 }
229
230 @Test
231 @Tag("schema")
232 void unconfiguredSchemaCompiles() {
233 AttackTestSupport.assertPermissiveSchemaCompiles(AttackTestSupport.streamSource(xsdPayload()));
234 }
235
236 @Test
237 @Tag("stax")
238 void unconfiguredStaxParses() {
239 AttackTestSupport.assertPermissiveStaxParses(xmlPayload());
240 }
241
242 @Test
243 @Tag("trax")
244 void unconfiguredTemplatesCompiles() {
245 AttackTestSupport.assertPermissiveTemplatesCompiles(xsltPayload());
246 }
247
248 @Test
249 @Tag("trax")
250 void unconfiguredTransformerTransforms() {
251 AttackTestSupport.assertPermissiveTransformerTransforms(xmlPayload());
252 }
253
254 @Test
255 @Tag("schema")
256 void unconfiguredValidatorValidates() {
257 AttackTestSupport.assertPermissiveValidatorValidates(xmlPayload());
258 }
259 }