View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.xml.secure;
19  
20  import javax.xml.XMLConstants;
21  import javax.xml.parsers.DocumentBuilderFactory;
22  import javax.xml.transform.TransformerFactory;
23  import javax.xml.validation.SchemaFactory;
24  
25  import org.junit.jupiter.api.Assumptions;
26  import org.junit.jupiter.api.Tag;
27  import org.junit.jupiter.api.Test;
28  import org.xml.sax.XMLReader;
29  
30  /**
31   * Tests that loosening a JAXP 1.5 {@code accessExternal*} property to {@code all} on a secured factory does not reopen an external fetch.
32   *
33   * <p>
34   * The threat model lists these properties under "Settings you may modify": the securing is independent of them, because a resource supplied by a resolver
35   * bypasses their checks and the resolver floor covers every external reference. The whole suite already runs with the {@code javax.xml.accessExternal*} system
36   * properties set to {@code all} (see the surefire configuration), so this test guards the one route the system properties cannot: a future recipe that set a
37   * property to the empty string and relied on it would be loosened by a caller's per-factory {@code all}, which no system-property-based run could detect.
38   * </p>
39   *
40   * <p>
41   * Payloads and expected outcomes mirror {@link ExternalDtdTest} (external DTD via {@code DOCTYPE SYSTEM}), {@link SchemaImportTest} ({@code xs:import}) and
42   * {@link TemplatesImportTest} ({@code xsl:import}). An implementation that rejects the property has no knob to loosen, so each set runs through
43   * {@link AttackTestSupport#assumeDoesNotThrow} and the test skips there (Android, external Apache Xerces).
44   * </p>
45   */
46  class AccessExternalPropertyTest {
47  
48      private static final String INSERTION = "&leaked;";
49  
50      private static String withDoctype(final String rootQName, final String body) {
51          return "<?xml version=\"1.0\"?>\n"
52                  + "<!DOCTYPE " + rootQName + " SYSTEM \"" + AttackTestSupport.resourceUrl("referenced.dtd") + "\">\n"
53                  + body + "\n";
54      }
55  
56      private static String xmlPayload() {
57          return withDoctype("root", AttackTestSupport.xmlBody(INSERTION));
58      }
59  
60      private static String xsdPayload() {
61          return withDoctype("xs:schema", AttackTestSupport.xsdBody(INSERTION));
62      }
63  
64      @Test
65      @Tag("dom")
66      void secureDomWithAccessExternalAllDoesNotLeak() {
67          Assumptions.assumeTrue(AttackTestSupport.DOM_RESOLVES_INTERNAL_ENTITIES,
68                  "Skipped: platform DOM does not resolve user-defined entities");
69          final DocumentBuilderFactory factory = SecureDocumentBuilderFactory.newInstance();
70          AttackTestSupport.assumeDoesNotThrow(() -> factory.setAttribute(TestConstants.ACCESS_EXTERNAL_DTD, "all"));
71          AttackTestSupport.assertDomDoesNotLeak(factory, xmlPayload());
72      }
73  
74      @Test
75      @Tag("sax")
76      void secureSaxWithAccessExternalAllDoesNotLeak() throws Exception {
77          final XMLReader reader = SecureSAXParserFactory.newInstance().newSAXParser().getXMLReader();
78          AttackTestSupport.assumeDoesNotThrow(() -> reader.setProperty(TestConstants.ACCESS_EXTERNAL_DTD, "all"));
79          AttackTestSupport.assertSaxDoesNotLeak(reader, xmlPayload());
80      }
81  
82      @Test
83      @Tag("schema")
84      void secureSchemaDoctypeWithAccessExternalAllDoesNotLeak() {
85          final SchemaFactory factory = SecureSchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
86          AttackTestSupport.assumeDoesNotThrow(() -> factory.setProperty(TestConstants.ACCESS_EXTERNAL_DTD, "all"));
87          AttackTestSupport.assertSchemaDoesNotLeak(factory, AttackTestSupport.streamSource(xsdPayload()));
88      }
89  
90      @Test
91      @Tag("schema")
92      void secureSchemaImportWithAccessExternalAllBlocks() {
93          final SchemaFactory factory = SecureSchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
94          AttackTestSupport.assumeDoesNotThrow(() -> factory.setProperty(TestConstants.ACCESS_EXTERNAL_SCHEMA, "all"));
95          AttackTestSupport.assertSchemaBlocks(factory, AttackTestSupport.resourceSource("with-import.xsd"));
96      }
97  
98      @Test
99      @Tag("trax")
100     void secureTemplatesImportWithAccessExternalAllDoesNotLeak() {
101         final TransformerFactory factory = SecureTransformerFactory.newInstance();
102         AttackTestSupport.assumeDoesNotThrow(() -> factory.setAttribute(TestConstants.ACCESS_EXTERNAL_STYLESHEET, "all"));
103         AttackTestSupport.assertTemplatesDoesNotLeak(factory, AttackTestSupport.resourceSource("with-import.xsl"));
104     }
105 
106     @Test
107     @Tag("trax")
108     void secureTransformerDoctypeWithAccessExternalAllDoesNotLeak() {
109         final TransformerFactory factory = SecureTransformerFactory.newInstance();
110         AttackTestSupport.assumeDoesNotThrow(() -> factory.setAttribute(TestConstants.ACCESS_EXTERNAL_DTD, "all"));
111         AttackTestSupport.assertTransformerDoesNotLeak(factory, xmlPayload());
112     }
113 }