• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011, Mike Samuel
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions
6 // are met:
7 //
8 // Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // Redistributions in binary form must reproduce the above copyright
11 // notice, this list of conditions and the following disclaimer in the
12 // documentation and/or other materials provided with the distribution.
13 // Neither the name of the OWASP nor the names of its contributors may
14 // be used to endorse or promote products derived from this software
15 // without specific prior written permission.
16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
19 // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
20 // COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
21 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
22 // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 // POSSIBILITY OF SUCH DAMAGE.
28 
29 package org.owasp.html;
30 
31 import com.google.common.base.Function;
32 import com.google.common.collect.Lists;
33 
34 import java.io.IOException;
35 import java.io.StringReader;
36 import java.util.List;
37 import java.util.Random;
38 
39 import org.w3c.dom.Attr;
40 import org.w3c.dom.NamedNodeMap;
41 import org.w3c.dom.Node;
42 import org.xml.sax.InputSource;
43 import org.xml.sax.SAXException;
44 
45 import nu.validator.htmlparser.dom.HtmlDocumentBuilder;
46 
47 /**
48  * Throws random policy calls to find evidence against the claim that the
49  * security of the policy is decoupled from that of the parser.
50  * This test is stochastic -- not guaranteed to pass or fail consistently.
51  * If you see a failure, please report it along with the seed from the output.
52  * If you want to repeat a failure, set the system property "junit.seed".
53  *
54  * @author Mike Samuel <mikesamuel@gmail.com>
55  */
56 public class HtmlPolicyBuilderFuzzerTest extends FuzzyTestCase {
57 
58   final Function<HtmlStreamEventReceiver, HtmlSanitizer.Policy> policyFactory
59       = new HtmlPolicyBuilder()
60       .allowElements("a", "b", "xmp", "pre")
61       .allowAttributes("href").onElements("a")
62       .allowAttributes("title").globally()
63       .allowStandardUrlProtocols()
64       .toFactory();
65 
66   static final String[] CHUNKS = {
67     "Hello, World!", "<b>", "</b>",
68     "<a onclick='doEvil()' href=javascript:alert(1337)>", "</a>",
69     "<script>", "</script>", "<xmp>", "</xmp>", "javascript:alert(1337)",
70     "<style>", "</style>", "<plaintext>", "<!--", "-->", "<![CDATA[", "]]>",
71   };
72 
73   static final String[] ELEMENT_NAMES = {
74     "a", "A",
75     "b", "B",
76     "script", "SCRipT",
77     "style", "STYLE",
78     "object", "Object",
79     "noscript", "noScript",
80     "xmp", "XMP",
81   };
82 
83   static final String[] ATTR_NAMES = {
84     "href", "id", "class", "onclick", "checked", "style",
85   };
86 
testFuzzedOutput()87   public final void testFuzzedOutput() throws IOException, SAXException {
88     boolean passed = false;
89     try {
90       for (int i = 1000; --i >= 0;) {
91         StringBuilder sb = new StringBuilder();
92         HtmlSanitizer.Policy policy = policyFactory.apply(
93             HtmlStreamRenderer.create(sb, Handler.DO_NOTHING));
94         policy.openDocument();
95         List<String> attributes = Lists.newArrayList();
96         for (int j = 50; --j >= 0;) {
97           int r = rnd.nextInt(3);
98           switch (r) {
99             case 0:
100               attributes.clear();
101               if (rnd.nextBoolean()) {
102                 for (int k = rnd.nextInt(4); --k >= 0;) {
103                   attributes.add(pick(rnd, ATTR_NAMES));
104                   attributes.add(pickChunk(rnd));
105                 }
106               }
107               policy.openTag(pick(rnd, ELEMENT_NAMES), attributes);
108               break;
109             case 1:
110               policy.closeTag(pick(rnd, ELEMENT_NAMES));
111               break;
112             case 2:
113               policy.text(pickChunk(rnd));
114               break;
115             default:
116               throw new AssertionError(
117                   "Randomly chosen number in [0-3) was " + r);
118           }
119         }
120         policy.closeDocument();
121 
122         String html = sb.toString();
123         HtmlDocumentBuilder parser = new HtmlDocumentBuilder();
124         Node node = parser.parseFragment(
125             new InputSource(new StringReader(html)), "body");
126         checkSafe(node, html);
127       }
128       passed = true;
129     } finally {
130       if (!passed) {
131         System.err.println("Using seed " + seed + "L");
132       }
133     }
134   }
135 
checkSafe(Node node, String html)136   private static void checkSafe(Node node, String html) {
137     switch (node.getNodeType()) {
138       case Node.ELEMENT_NODE:
139         String name = node.getNodeName();
140         if (!"a".equals(name) && !"b".equals(name) && !"pre".equals(name)) {
141           fail("Illegal element name " + name + " : " + html);
142         }
143         NamedNodeMap attrs = node.getAttributes();
144         for (int i = 0, n = attrs.getLength(); i < n; ++i) {
145           Attr a = (Attr) attrs.item(i);
146           if ("title".equals(a.getName())) {
147             // ok
148           } else if ("href".equals(a.getName())) {
149             assertEquals(html, "a", name);
150             assertFalse(
151                 html, Strings.toLowerCase(a.getValue()).contains("script:"));
152           }
153         }
154         break;
155     }
156     for (Node child = node.getFirstChild(); child != null;
157          child = child.getNextSibling()) {
158       checkSafe(child, html);
159     }
160   }
161 
pick(Random rnd, String[] choices)162   private static String pick(Random rnd, String[] choices) {
163     return choices[rnd.nextInt(choices.length)];
164   }
165 
pickChunk(Random rnd)166   private static String pickChunk(Random rnd) {
167     String chunk = pick(rnd, CHUNKS);
168     int start = 0;
169     int end = chunk.length();
170     if (rnd.nextBoolean()) {
171       start = rnd.nextInt(end - 1);
172     }
173     if (end - start < 2 && rnd.nextBoolean()) {
174       end = start + rnd.nextInt(end - start);
175     }
176     return chunk.substring(start, end);
177   }
178 }
179