This is an automated email from the ASF dual-hosted git repository.
radu pushed a commit to branch issue/SLING-7741_v2
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-xss.git
The following commit(s) were added to refs/heads/issue/SLING-7741_v2 by this
push:
new 29f28e9 SLING-7741 -
org.apache.sling.xss.impl.XSSAPIImpl#getValidHref doesn't correctly handle the
":" character in URL fragments
29f28e9 is described below
commit 29f28e9cfd47a532dca1bd252fc2544529429a66
Author: Radu Cotescu <[email protected]>
AuthorDate: Fri Jun 29 17:40:57 2018 +0200
SLING-7741 - org.apache.sling.xss.impl.XSSAPIImpl#getValidHref doesn't
correctly handle the ":" character in URL fragments
* updated dependencies and provided more tests
---
pom.xml | 27 +--
src/main/resources/SLING-INF/content/config.xml | 4 +-
.../apache/sling/xss/impl/AntiSamyPolicyTest.java | 256 +++++++++++++++++++++
3 files changed, 263 insertions(+), 24 deletions(-)
diff --git a/pom.xml b/pom.xml
index fbaa41e..14d896d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -90,6 +90,7 @@
!org.apache.tools.ant.taskdefs,
!org.apache.xml.resolver,
!org.apache.xml.resolver.readers,
+ !org.apache.xmlgraphics.java2d.color,
!org.apache.log,
!javax.mail.internet,
!javax.servlet.jsp,
@@ -167,33 +168,15 @@
<!-- reconstruct the full list here. -->
<!-- TODO: Remove this workaround when we dump Java 5. -->
<dependency>
- <groupId>batik</groupId>
+ <groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-css</artifactId>
- <version>1.6</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>batik</groupId>
- <artifactId>batik-ext</artifactId>
- <version>1.6</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>batik</groupId>
- <artifactId>batik-util</artifactId>
- <version>1.6</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>batik</groupId>
- <artifactId>batik-gui-util</artifactId>
- <version>1.6</version>
+ <version>1.9.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>xml-apis</groupId>
- <artifactId>xml-apis-ext</artifactId>
- <version>1.3.04</version>
+ <artifactId>xml-apis</artifactId>
+ <version>1.4.01</version>
<scope>provided</scope>
</dependency>
<!-- </#40108 - XSS protection does not work on Java 5> -->
diff --git a/src/main/resources/SLING-INF/content/config.xml
b/src/main/resources/SLING-INF/content/config.xml
index e1cd3f4..b57a4fa 100644
--- a/src/main/resources/SLING-INF/content/config.xml
+++ b/src/main/resources/SLING-INF/content/config.xml
@@ -109,8 +109,8 @@ http://www.w3.org/TR/html401/struct/global.html
<regexp name="cssAttributeExclusion" value=""/>
<!-- This is for resources referenced from CSS (such as background
images and other imported stylesheets) -->
- <regexp name="cssOnsiteUri"
value="url\(([\p{L}\p{N}\\/\.\?=\#&;\-_~]+|\#(\w)+)\)"/>
- <regexp name="cssOffsiteUri"
value="url\((\s)*((ht|f)tp(s?)://)[\p{L}\p{N}]+[~\p{L}\p{N}\p{Zs}\-_\.@#$%&;:,\?=/\+!]*(\s)*\)"/>
+ <regexp name="cssOnsiteUri"
value="url\((?!\s*javascript(?::|&colon;))(?:(?://(?:(?:(?:(?:\p{L}\p{M}*)|[\p{N}-._~])|(?:%\p{XDigit}\p{XDigit})|(?:[!$&'()*+,;=]))*@)?(?:\[(?:(?:(?:\p{XDigit}{1,4}:){6}(?:(?:\p{XDigit}{1,4}:\p{XDigit}{1,4})|(?:\p{N}|[\x31-\x39]\p{N}|1\p{N}{2}|2[\x30-\x34]\p{N}|25[\x30-\x35])\.(?:\p{N}|[\x31-\x39]\p{N}|1\p{N}{2}|2[\x30-\x34]\p{N}|25[\x30-\x35])\.(?:\p{N}|[\x31-\x39]\p{N}|1\p{N}{2}|2[\x30-\x34]\p{N}|25[\x30-\x35])\.(?:\p{N}|[\x31-\x39]\p{N
[...]
+ <regexp name="cssOffsiteUri"
value="url\((?!\s*javascript)\p{L}[\p{L}\p{N}+.\-]*:(?:(?://(?:(?:(?:(?:\p{L}\p{M}*)|[\p{N}-._~])|(?:%\p{XDigit}\p{XDigit})|(?:[!$&'()*+,;=]))*@)?(?:\[(?:(?:(?:\p{XDigit}{1,4}:){6}(?:(?:\p{XDigit}{1,4}:\p{XDigit}{1,4})|(?:\p{N}|[\x31-\x39]\p{N}|1\p{N}{2}|2[\x30-\x34]\p{N}|25[\x30-\x35])\.(?:\p{N}|[\x31-\x39]\p{N}|1\p{N}{2}|2[\x30-\x34]\p{N}|25[\x30-\x35])\.(?:\p{N}|[\x31-\x39]\p{N}|1\p{N}{2}|2[\x30-\x34]\p{N}|25[\x30-\x35])\.(?:\p{N}|[\x31-\x
[...]
<!-- This if for CSS Identifiers -->
<regexp name="cssIdentifier" value="[a-zA-Z0-9\-_]+"/>
diff --git a/src/test/java/org/apache/sling/xss/impl/AntiSamyPolicyTest.java
b/src/test/java/org/apache/sling/xss/impl/AntiSamyPolicyTest.java
new file mode 100644
index 0000000..f39329b
--- /dev/null
+++ b/src/test/java/org/apache/sling/xss/impl/AntiSamyPolicyTest.java
@@ -0,0 +1,256 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.owasp.validator.html.AntiSamy;
+import org.owasp.validator.html.Policy;
+import org.owasp.validator.html.PolicyException;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * This test suite makes sure the customised {@code config.xml} policy shipped
with this module is not exposed to attacks. The test strings
+ * are adapted from <a
href="https://github.com/nahsra/antisamy/blob/master/src/test/java/org/owasp/validator/html/test/AntiSamyTest.java">
+ *
https://github.com/nahsra/antisamy/blob/master/src/test/java/org/owasp/validator/html/test/AntiSamyTest.java</a>.
+ */
+public class AntiSamyPolicyTest {
+
+ public static final String POLICY_FILE = "SLING-INF/content/config.xml";
+ private static AntiSamy antiSamy;
+
+ @BeforeClass
+ public static void setup() throws PolicyException {
+ antiSamy = new
AntiSamy(Policy.getInstance(AntiSamyPolicyTest.class.getClassLoader().getResourceAsStream(POLICY_FILE)));
+ }
+
+ @Test
+ public void testScriptFiltering() throws Exception {
+ TestInput[] tests = new TestInput[]{
+ new TestInput("test<script>alert(document.cookie)</script>",
"script", false),
+ new TestInput("<<<><<script src=http://fake-evil.ru/test.js>",
"<script", false),
+ new TestInput("<script<script
src=http://fake-evil.ru/test.js>>", "<script", false),
+ new TestInput("<SCRIPT/XSS
SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT>", "<script", false),
+ new
TestInput("<![CDATA[]><script>alert(1)</script><![CDATA[]>]]><script>alert(2)</script>>]]>",
"<script", false),
+
+ };
+ for (TestInput testInput : tests) {
+ testOutputContains(testInput.input,
testInput.expectedPartialOutput, testInput.containsExpectedPartialOutput);
+ }
+ }
+
+ @Test
+ public void testEventHandlerAttributes() throws Exception {
+ TestInput[] tests = new TestInput[]{
+ new TestInput("<a onblur=\"alert(secret)\"
href=\"http://www.google.com\">Google</a>", "onblur", false),
+ new TestInput("<BODY
onload!#$%&()*~+-_.,:;?@[/|\\]^`=alert(\"XSS\")>", "onload", false),
+ new TestInput("<BODY ONLOAD=alert('XSS')>", "alert", false),
+ new TestInput("<a
href=\"http://example.com\"&/onclick=alert(9)>foo</a>", "onclick", false),
+ new TestInput("<style onload=alert(1)>h1
{color:red;}</style>", "onload", false),
+ new TestInput("<bogus>whatever</bogus><img
src=\"https://ssl.gstatic.com/codesite/ph/images/defaultlogo.png\" " +
+ "onmouseover=\"alert('xss')\">", "onmouseover", false),
+ };
+ for (TestInput testInput : tests) {
+ testOutputContains(testInput.input,
testInput.expectedPartialOutput, testInput.containsExpectedPartialOutput);
+ }
+ }
+
+ @Test
+ public void testImageFiltering() throws Exception {
+ TestInput[] tests = new TestInput[]{
+ new TestInput("<img src=\"http://www.myspace.com/img.gif\"/>",
"<img", true),
+ new TestInput("<img src=javascript:alert(document.cookie)>",
"<img", false),
+ new TestInput(
+ "<IMG
SRC=javascript:alert('XSS')>",
+ "<img", false),
+ new TestInput("<IMG SRC=\"jav
ascript:alert('XSS');\">",
"alert", false),
+ new TestInput("<IMG SRC=\"javascript:alert('XSS')\"",
"javascript", false),
+ new TestInput("<IMG LOWSRC=\"javascript:alert('XSS')\">",
"javascript", false),
+ };
+ for (TestInput testInput : tests) {
+ testOutputContains(testInput.input,
testInput.expectedPartialOutput, testInput.containsExpectedPartialOutput);
+ }
+
+ String[] emptyOutput = new String[]{
+ "<IMG
SRC=javascript:a"
+
+
"lert('XSS')>",
+ "<IMG
SRC=javascript:alert('XSS')>"
+ };
+ for (String input : emptyOutput) {
+ testOutpuIsEmpty(input);
+ }
+ }
+
+ @Test
+ public void testURIFiltering() throws Exception {
+ TestInput[] testInputs = new TestInput[]{
+ new TestInput("<INPUT TYPE=\"IMAGE\"
SRC=\"javascript:alert('XSS');\">", "src", false),
+ new TestInput("<iframe src=http://ha.ckers.org/scriptlet.html
<", "<iframe", false),
+ new TestInput("<LINK REL=\"stylesheet\"
HREF=\"javascript:alert('XSS');\">", "href", false),
+ new TestInput("<LINK REL=\"stylesheet\"
HREF=\"http://ha.ckers.org/xss.css\">", "href", false),
+ new
TestInput("<STYLE>@import'http://ha.ckers.org/xss.css';</STYLE>",
"ha.ckers.org", false),
+ new
TestInput("<STYLE>BODY{-moz-binding:url(\"http://ha.ckers.org/xssmoz.xml#xss\")}</STYLE>",
"ha.ckers.org", false),
+ new TestInput("<STYLE>li {list-style-image:
url(\"javascript:alert('XSS')\");}</STYLE><UL><LI>XSS", "javascript", false),
+ new TestInput("<IMG SRC='vbscript:msgbox(\"XSS\")'>",
"vbscript", false),
+ new TestInput("<META HTTP-EQUIV=\"refresh\" CONTENT=\"0;
URL=http://;URL=javascript:alert('XSS');\">", "<meta", false),
+ new TestInput("<META HTTP-EQUIV=\"refresh\"
CONTENT=\"0;url=javascript:alert('XSS');\">", "<meta", false),
+ new TestInput(
+ "<META HTTP-EQUIV=\"refresh\"
CONTENT=\"0;url=data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K\">",
+ "<meta", false),
+ new TestInput("<IFRAME
SRC=\"javascript:alert('XSS');\"></IFRAME>", "<iframe", false),
+ new TestInput("<FRAMESET><FRAME
SRC=\"javascript:alert('XSS');\"></FRAMESET>", "javascript", false),
+ new TestInput("<TABLE
BACKGROUND=\"javascript:alert('XSS')\">", "background", false),
+ new TestInput("<TABLE><TD
BACKGROUND=\"javascript:alert('XSS')\">", "background", false),
+ new TestInput("<DIV STYLE=\"background-image:
url(javascript:alert('XSS'))\">", "javascript", false),
+ new TestInput("<DIV STYLE=\"width:
expression(alert('XSS'));\">", "alert", false),
+ new TestInput("<IMG
STYLE=\"xss:expr/*XSS*/ession(alert('XSS'))\">", "alert", false),
+ new
TestInput("<STYLE>@im\\port'\\ja\\vasc\\ript:alert(\"XSS\")';</STYLE>",
"ript:alert", false),
+ new TestInput("<BASE HREF=\"javascript:alert('XSS');//\">",
"javascript", false),
+ new TestInput("<BaSe hReF=\"http://arbitrary.com/\">",
"<base", false),
+ new TestInput("<OBJECT TYPE=\"text/x-scriptlet\"
DATA=\"http://ha.ckers.org/scriptlet.html\"></OBJECT>", "<object", false),
+ new TestInput(
+ "<OBJECT
classid=clsid:ae24fdae-03c6-11d1-8b76-0080c744f389><param name=url
value=javascript:alert('XSS')></OBJECT>",
+ "javascript", false),
+ new TestInput("<EMBED SRC=\"http://ha.ckers.org/xss.swf\"
AllowScriptAccess=\"always\"></EMBED>", "<embed", false),
+ new TestInput(
+ "<EMBED
SRC=\"data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH
A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv
MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs
aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw
IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh
TUyIpOzwvc2NyaXB0Pjwvc3ZnPg==\" type=\"image/svg+xml\"
AllowScriptAccess=\"always\"></EMBED>",
+ "<embed", false),
+ new TestInput("<SCRIPT a=\">\"
SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT>", "<script", false),
+ new TestInput("<SCRIPT a=\">\" ''
SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT>", "<script", false),
+ new TestInput("<SCRIPT a=`>`
SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT>", "<script", false),
+ new TestInput("<SCRIPT a=\">'>\"
SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT>", "<script", false),
+ new TestInput("<SCRIPT>document.write(\"<SCRI\");</SCRIPT>PT
SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT>", "<script",
+ false),
+ new TestInput("<SCRIPT SRC=http://ha.ckers.org/xss.js",
"<script", false),
+ new TestInput(
+
"<div/style=\-\mo\z\-b\i\nd\in\g:\url(//business\i\nfo.co.uk\/labs\/xbl\/xbl\.xml\#xss)&>",
+ "style", false),
+ new TestInput("<a href='aim: &c:\\windows\\system32\\calc.exe'
ini='C:\\Documents and Settings\\All Users\\Start " +
+ "Menu\\Programs\\Startup\\pwnd.bat'>", "calc.exe",
false),
+ new TestInput("<!--\n<A href=\n- --><a
href=javascript:alert:document.domain>test-->", "javascript", false),
+ new TestInput(
+ "<a></a
style=\"\"xx:expr/**/ession(document.appendChild(document.createElement('script')).src='http://h4k.in/i.js')\">",
+ "document", false),
+ new TestInput("<a
href='http://subdomain.domain/(S(ke0lpq54bw0fvp53a10e1a45))/MyPage.aspx'>test</a>",
"http://subdomain" +
+ ".domain/(S(ke0lpq54bw0fvp53a10e1a45))/MyPage.aspx",
true),
+ new TestInput("<a
href=\"javascript:alert(1)\">X</a>", "javascript", false)
+
+ };
+ for (TestInput testInput : testInputs) {
+ testOutputContains(testInput.input,
testInput.expectedPartialOutput, testInput.containsExpectedPartialOutput);
+ }
+ }
+
+ @Test
+ public void testCSSFiltering() throws Exception {
+ TestInput[] testInputs = new TestInput[]{
+ new TestInput("<div style=\"position:absolute\">", "position",
false),
+ new TestInput("<style>b { position:absolute }</style>",
"position", false),
+ new TestInput("<div style=\"z-index:25\">test</div>",
"z-index", false),
+ new TestInput("<style>z-index:25</style>", "z-index", false),
+ new TestInput("<div style=\"margin: -5em\">Test</div>",
"margin", false),
+ new TestInput("<div style=\"font-family: Geneva, Arial,
courier new, sans-serif\">Test</div>", "font-family", true),
+ new TestInput("<style type=\"text/css\"><![CDATA[P {\n
font-family: \"Arial Unicode MS\";\n}\n]]></style>",
+ "font-family", true),
+ new TestInput("<style type=\"text/css\"><![CDATA[P {
margin-bottom: 0.08in; } ]]></style>", "margin-bottom", true),
+ new TestInput("<style type=\"text/css\"><![CDATA[\r\nP {\r\n
margin-bottom: 0.08in;\r\n}\r\n]]></style>", "margin-bottom",
+ true),
+ new TestInput("<style>P {\n\tmargin-bottom: 0.08in;\n}\n",
"margin-bottom", true),
+ new TestInput("<font color=\"#fff\">Test</font>",
"color=\"#fff\"", true),
+ new TestInput("<font color=\"red\">Test</font>",
"color=\"red\"", true),
+ new TestInput("<font color=\"neonpink\">Test</font>", "color",
false),
+ new TestInput("<font color=\"#0000\">Test</font>", "color=",
false),
+ new TestInput("<font color=\"#000000\">Test</font>",
"color=\"#000000\"", true),
+ };
+ for (TestInput testInput : testInputs) {
+ testOutputContains(testInput.input,
testInput.expectedPartialOutput, testInput.containsExpectedPartialOutput);
+ }
+ testOutputContains("<div style=\"color: #fff\">Test 3 letter
code</div>", "color: rgb(255,255,255)", true, true);
+ testOutputContains("<div style=\"color: #000000\">Test</div>", "color:
rgb(0,0,0)", true, true);
+ testOutputContains("<div style=\"color: #0000\">Test</div>",
"style=\"\"", true, true);
+ }
+
+ private void testOutputContains(String input, String containedString,
boolean contains) throws Exception {
+ testOutputContains(input, containedString, contains, false);
+ }
+
+ private void testOutputContains(String input, String containedString,
boolean contains, boolean skipComparingInputWithOutput) throws Exception {
+ testOutputContains(input, containedString, contains,
skipComparingInputWithOutput, Mode.SAX_AND_DOM);
+ }
+
+ private void testOutputContains(String input, String containedString,
boolean contains, boolean skipComparingInputWithOutput,
+ Mode mode) throws Exception {
+ String cleanDOMModeHTML = antiSamy.scan(input,
AntiSamy.DOM).getCleanHTML();
+ String cleanSAXModeHTML = antiSamy.scan(input,
AntiSamy.SAX).getCleanHTML();
+ if (!skipComparingInputWithOutput) {
+ assertTrue(String.format("Test is not properly configured: input
'%s' doesn't seem to contain '%s' (case-insensitive match).",
+ input, containedString),
input.toLowerCase().contains(containedString.toLowerCase()));
+ }
+ if (contains) {
+ if (mode == Mode.DOM || mode == Mode.SAX_AND_DOM) {
+ assertTrue(
+ String.format("Expected that DOM filtered output '%s'
for input '%s' would contain '%s'.", cleanDOMModeHTML, input,
+ containedString), antiSamy.scan(input,
AntiSamy.DOM).getCleanHTML().contains(containedString));
+ }
+ if (mode == Mode.SAX || mode == Mode.SAX_AND_DOM) {
+ assertTrue(String.format("Expected that SAX filtered output
'%s' for input '%s' would contain '%s'.", cleanSAXModeHTML,
+ input,
+ containedString), antiSamy.scan(input,
AntiSamy.SAX).getCleanHTML().contains(containedString));
+ }
+ } else {
+ if (mode == Mode.DOM || mode == Mode.SAX_AND_DOM) {
+ assertFalse(
+ String.format("Expected that DOM filtered output '%s'
for input '%s', would NOT contain '%s'.", cleanDOMModeHTML,
+ input, containedString), antiSamy.scan(input,
AntiSamy.DOM).getCleanHTML().contains(containedString));
+ }
+ if (mode == Mode.SAX || mode == Mode.SAX_AND_DOM) {
+ assertFalse(String.format("Expected that SAX filtered output
'%s' for input '%s' would NOT contain '%s'.", cleanSAXModeHTML,
+ input, containedString), antiSamy.scan(input,
AntiSamy.SAX).getCleanHTML().contains(containedString));
+ }
+ }
+ }
+
+
+ private void testOutpuIsEmpty(String input) throws Exception {
+ String cleanDOMModeHTML = antiSamy.scan(input,
AntiSamy.DOM).getCleanHTML();
+ String cleanSAXModeHTML = antiSamy.scan(input,
AntiSamy.SAX).getCleanHTML();
+ assertTrue("Expected empty DOM filtered output for '" + input + "'.",
StringUtils.isEmpty(cleanDOMModeHTML));
+ assertTrue("Expected empty SAX filtered output for '" + input + "'.",
StringUtils.isEmpty(cleanSAXModeHTML));
+ }
+
+ private class TestInput {
+ String input;
+ String expectedPartialOutput;
+ boolean containsExpectedPartialOutput;
+
+ public TestInput(String input, String expectedPartialOutput, boolean
containsExpectedPartialOutput) {
+ this.input = input;
+ this.expectedPartialOutput = expectedPartialOutput;
+ this.containsExpectedPartialOutput = containsExpectedPartialOutput;
+ }
+ }
+
+ private enum Mode {
+ SAX, DOM, SAX_AND_DOM;
+ }
+}
+