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 static org.junit.jupiter.api.Assertions.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertNotNull;
22  import static org.junit.jupiter.api.Assertions.assertSame;
23  import static org.junit.jupiter.api.Assertions.assertTrue;
24  
25  import java.io.StringWriter;
26  import java.util.Properties;
27  
28  import javax.xml.transform.Templates;
29  import javax.xml.transform.Transformer;
30  import javax.xml.transform.TransformerConfigurationException;
31  import javax.xml.transform.TransformerFactory;
32  import javax.xml.transform.URIResolver;
33  import javax.xml.transform.sax.SAXTransformerFactory;
34  import javax.xml.transform.sax.TransformerHandler;
35  import javax.xml.transform.stream.StreamResult;
36  
37  import org.junit.jupiter.api.Assumptions;
38  import org.junit.jupiter.api.Tag;
39  import org.junit.jupiter.api.Test;
40  
41  /**
42   * {@link TransformerHandler} products of the secure factory sit on the resolver floor: a stylesheet's runtime {@code document()} resolves to empty content
43   * whether the transform runs through the handler's SAX events or through the {@link TransformerHandler#getTransformer()} bypass. The unconfigured control
44   * proves the vector leaks without the securing. The {@code Templates} overload doubles as a regression test for handing the factory a wrapped
45   * {@code Templates} (implementations cast its {@code newTransformer()} to their own type).
46   */
47  @Tag("trax")
48  class TransformerHandlerTest {
49  
50      /**
51       * Skips the test where the implementation refuses a Templates it did not compile itself, as Saxon does.
52       */
53      private static TransformerHandler assumeAcceptsForeignImplementation(final Templates callers) {
54          try {
55              return ((SAXTransformerFactory) TransformerFactory.newInstance()).newTransformerHandler(callers);
56          } catch (final TransformerConfigurationException e) {
57              Assumptions.abort("the implementation does not accept a foreign Templates: " + e.getMessage());
58              return null;
59          }
60      }
61  
62      /**
63       * A caller's own Templates that only configures the Transformer it hands out, the shape Apache CXF's XSLTJaxbProvider builds.
64       */
65      private static Templates callersTemplates(final Templates compiled, final URIResolver carried) {
66          return new Templates() {
67  
68              @Override
69              public Properties getOutputProperties() {
70                  return compiled.getOutputProperties();
71              }
72  
73              @Override
74              public Transformer newTransformer() throws TransformerConfigurationException {
75                  final Transformer transformer = compiled.newTransformer();
76                  transformer.setURIResolver(carried);
77                  return transformer;
78              }
79          };
80      }
81  
82      private static String transformViaHandler(final TransformerHandler handler) throws Exception {
83          final StringWriter sink = new StringWriter();
84          handler.setResult(new StreamResult(sink));
85          SaxSurfaceTestSupport.feed(handler, SaxSurfaceTestSupport.rootInput());
86          return sink.toString();
87      }
88  
89      @Test
90      void secureGetTransformerDoesNotLeakDocument() throws Exception {
91          // The f004 bypass: pull the inner Transformer out of the handler and transform directly; the floor must ride along.
92          final SAXTransformerFactory factory = SaxSurfaceTestSupport.secureFactory();
93          final TransformerHandler handler = factory.newTransformerHandler(AttackTestSupport.resourceSource("with-document.xsl"));
94          final StringWriter sink = new StringWriter();
95          handler.getTransformer().transform(AttackTestSupport.streamSource("<root/>"), new StreamResult(sink));
96          assertFalse(sink.toString().contains(AttackTestSupport.LEAKED_MARKER), "document() through getTransformer() leaked");
97      }
98  
99      @Test
100     void secureHandlerKeepsAResolverTheCallersTemplatesSet() throws Exception {
101         final Templates compiled = TransformerFactory.newInstance().newTemplates(AttackTestSupport.resourceSource("with-document.xsl"));
102         final URIResolver carried = (href, base) -> null;
103         final Templates callers = callersTemplates(compiled, carried);
104         // What the implementation itself ends up with: XSLTC leaves the caller's resolver in place, Apache Xalan overwrites it with its factory's own.
105         final TransformerHandler nativeTransformerHandler = assumeAcceptsForeignImplementation(callers);
106         Assumptions.assumeTrue(nativeTransformerHandler.getTransformer().getURIResolver() == carried,
107                 "the implementation does not keep a resolver set by the caller's Templates");
108 
109         final TransformerHandler handler = SaxSurfaceTestSupport.secureFactory().newTransformerHandler(callers);
110         assertSame(carried, handler.getTransformer().getURIResolver(), "the securing must not lose a resolver the implementation kept");
111         assertFalse(transformViaHandler(handler).contains(AttackTestSupport.LEAKED_MARKER), "what that resolver declined must not be fetched");
112     }
113 
114     @Test
115     void secureTransformerHandlerDoesNotLeakDocument() throws Exception {
116         final SAXTransformerFactory factory = SaxSurfaceTestSupport.secureFactory();
117         final TransformerHandler handler = factory.newTransformerHandler(AttackTestSupport.resourceSource("with-document.xsl"));
118         assertFalse(transformViaHandler(handler).contains(AttackTestSupport.LEAKED_MARKER), "document() through TransformerHandler leaked");
119     }
120 
121     @Test
122     void secureTransformerHandlerFromTemplatesDoesNotLeakDocument() throws Exception {
123         final SAXTransformerFactory factory = SaxSurfaceTestSupport.secureFactory();
124         final Templates templates = factory.newTemplates(AttackTestSupport.resourceSource("with-document.xsl"));
125         assertNotNull(templates, "stylesheet failed to compile");
126         final TransformerHandler handler = factory.newTransformerHandler(templates);
127         assertFalse(transformViaHandler(handler).contains(AttackTestSupport.LEAKED_MARKER),
128                 "document() through TransformerHandler(Templates) leaked");
129     }
130 
131     @Test
132     void unconfiguredTransformerHandlerLeaksDocument() throws Exception {
133         final SAXTransformerFactory factory = (SAXTransformerFactory) TransformerFactory.newInstance();
134         final TransformerHandler handler = factory.newTransformerHandler(AttackTestSupport.resourceSource("with-document.xsl"));
135         assertTrue(transformViaHandler(handler).contains(AttackTestSupport.LEAKED_MARKER),
136                 "unconfigured TransformerHandler should resolve document()");
137     }
138 }