You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ra...@apache.org on 2015/05/13 15:40:08 UTC
svn commit: r1679207 - in /sling/trunk/bundles/extensions/xss/src:
main/java/org/apache/sling/xss/ main/java/org/apache/sling/xss/impl/
test/java/org/apache/sling/xss/impl/
Author: radu
Date: Wed May 13 13:40:07 2015
New Revision: 1679207
URL: http://svn.apache.org/r1679207
Log:
SLING-4557 - Add JSON and XML validation to the XSS Protection API
* Added validator methods to the API
* Added JSON validation implementation using Apache Commons JSON
* Added XML validation implementation using SAX
* Added unit tests
(applied slightly modified patch from Vlad Bailescu; closes #81)
Modified:
sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/XSSAPI.java
sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/impl/XSSAPIImpl.java
sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/package-info.java
sling/trunk/bundles/extensions/xss/src/test/java/org/apache/sling/xss/impl/XSSAPIImplTest.java
Modified: sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/XSSAPI.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/XSSAPI.java?rev=1679207&r1=1679206&r2=1679207&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/XSSAPI.java (original)
+++ sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/XSSAPI.java Wed May 13 13:40:07 2015
@@ -1,4 +1,5 @@
-/*******************************************************************************
+/**
+ * ****************************************************************************
* 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
@@ -6,14 +7,15 @@
* 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
- *
+ * <p/>
* 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;
@@ -52,7 +54,7 @@ public interface XSSAPI {
* @return a sanitized integer
*/
@Nullable
- public Integer getValidInteger(@Nullable String integer, @Nullable int defaultValue);
+ Integer getValidInteger(@Nullable String integer, int defaultValue);
/**
* Validate a string which should contain a long, returning a default value if the source is
@@ -63,7 +65,7 @@ public interface XSSAPI {
* @return a sanitized integer
*/
@Nullable
- public Long getValidLong(@Nullable String source, @Nullable long defaultValue);
+ Long getValidLong(@Nullable String source,long defaultValue);
/**
* Validate a string which should contain a dimension, returning a default value if the source is
@@ -74,7 +76,7 @@ public interface XSSAPI {
* @return a sanitized dimension
*/
@Nullable
- public String getValidDimension(@Nullable String dimension, @Nullable String defaultValue);
+ String getValidDimension(@Nullable String dimension, @Nullable String defaultValue);
/**
* Sanitizes a URL for writing as an HTML href or src attribute value.
@@ -83,7 +85,7 @@ public interface XSSAPI {
* @return a sanitized URL (possibly empty)
*/
@Nonnull
- public String getValidHref(@Nullable String url);
+ String getValidHref(@Nullable String url);
/**
* Validate a Javascript token. The value must be either a single identifier, a literal number,
@@ -94,7 +96,7 @@ public interface XSSAPI {
* @return a string containing a single identifier, a literal number, or a literal string token
*/
@Nullable
- public String getValidJSToken(@Nullable String token, @Nullable String defaultValue);
+ String getValidJSToken(@Nullable String token, @Nullable String defaultValue);
/**
* Validate a style/CSS token. Valid CSS tokens are specified at http://www.w3.org/TR/css3-syntax/
@@ -105,7 +107,7 @@ public interface XSSAPI {
* @return a string containing sanitized style token
*/
@Nullable
- public String getValidStyleToken(@Nullable String token, @Nullable String defaultValue);
+ String getValidStyleToken(@Nullable String token, @Nullable String defaultValue);
/**
* Validate a CSS color value. Color values as specified at http://www.w3.org/TR/css3-color/#colorunits
@@ -117,17 +119,35 @@ public interface XSSAPI {
* @return a string a css color value.
*/
@Nullable
- public String getValidCSSColor(@Nullable String color, @Nullable String defaultColor);
+ String getValidCSSColor(@Nullable String color, @Nullable String defaultColor);
/**
- * Validate multiline comment to be used inside a <script>...</script> or <style>...</style> block. Multiline
+ * Validate multi-line comment to be used inside a <script>...</script> or <style>...</style> block. Multi-line
* comment end block is disallowed
*
* @param comment the comment to be used
* @param defaultComment a default value to use if the comment is {@code null} or not valid.
- * @return a valid multiline comment
+ * @return a valid multi-line comment
+ */
+ String getValidMultiLineComment(@Nullable String comment, @Nullable String defaultComment);
+
+ /**
+ * Validate a JSON string
+ *
+ * @param json the JSON string to validate
+ * @param defaultJson the default value to use if {@code json} is {@code null} or not valid
+ * @return a valid JSON string
+ */
+ String getValidJSON(@Nullable String json, @Nullable String defaultJson);
+
+ /**
+ * Validate an XML string
+ *
+ * @param xml the XML string to validate
+ * @param defaultXml the default value to use if {@code xml} is {@code null} or not valid
+ * @return a valid XML string
*/
- public String getValidMultiLineComment(@Nullable String comment, @Nullable String defaultComment);
+ String getValidXML(@Nullable String xml, @Nullable String defaultXml);
// =============================================================================================
// ENCODERS
@@ -141,7 +161,7 @@ public interface XSSAPI {
* @return an encoded version of the source
*/
@Nullable
- public String encodeForHTML(@Nullable String source);
+ String encodeForHTML(@Nullable String source);
/**
* Encodes a source string for writing to an HTML attribute value.
@@ -151,7 +171,7 @@ public interface XSSAPI {
* @return an encoded version of the source
*/
@Nullable
- public String encodeForHTMLAttr(@Nullable String source);
+ String encodeForHTMLAttr(@Nullable String source);
/**
* Encodes a source string for XML element content.
@@ -161,7 +181,7 @@ public interface XSSAPI {
* @return an encoded version of the source
*/
@Nullable
- public String encodeForXML(@Nullable String source);
+ String encodeForXML(@Nullable String source);
/**
* Encodes a source string for writing to an XML attribute value.
@@ -170,7 +190,7 @@ public interface XSSAPI {
* @return an encoded version of the source
*/
@Nullable
- public String encodeForXMLAttr(@Nullable String source);
+ String encodeForXMLAttr(@Nullable String source);
/**
* Encodes a source string for writing to JavaScript string content.
@@ -181,7 +201,7 @@ public interface XSSAPI {
* @return an encoded version of the source
*/
@Nullable
- public String encodeForJSString(@Nullable String source);
+ String encodeForJSString(@Nullable String source);
/**
* Encodes a source string for writing to CSS string content.
@@ -192,7 +212,7 @@ public interface XSSAPI {
* @return an encoded version of the source
*/
@Nullable
- public String encodeForCSSString(@Nullable String source);
+ String encodeForCSSString(@Nullable String source);
// =============================================================================================
@@ -207,7 +227,7 @@ public interface XSSAPI {
* @return a string containing the sanitized HTML which may be an empty string if {@code source} is {@code null} or empty
*/
@Nonnull
- public String filterHTML(@Nullable String source);
+ String filterHTML(@Nullable String source);
// =============================================================================================
@@ -221,7 +241,7 @@ public interface XSSAPI {
* @param request the request from which to obtain the {@link org.apache.sling.xss.XSSAPI}
* @return an XSSAPI service capable of validating hrefs.
*/
- public XSSAPI getRequestSpecificAPI(SlingHttpServletRequest request);
+ XSSAPI getRequestSpecificAPI(SlingHttpServletRequest request);
/**
* Returns an XSSAPI instance capable of mapping resource URLs.
@@ -230,6 +250,6 @@ public interface XSSAPI {
* @param resourceResolver the resolver from which to obtain the {@link org.apache.sling.xss.XSSAPI}
* @return an XSSAPI service capable of validating hrefs.
*/
- public XSSAPI getResourceResolverSpecificAPI(ResourceResolver resourceResolver);
+ XSSAPI getResourceResolverSpecificAPI(ResourceResolver resourceResolver);
}
Modified: sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/impl/XSSAPIImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/impl/XSSAPIImpl.java?rev=1679207&r1=1679206&r2=1679207&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/impl/XSSAPIImpl.java (original)
+++ sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/impl/XSSAPIImpl.java Wed May 13 13:40:07 2015
@@ -16,28 +16,39 @@
******************************************************************************/
package org.apache.sling.xss.impl;
+import java.io.StringReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import javax.annotation.Nonnull;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.commons.json.JSONArray;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.commons.json.JSONObject;
import org.apache.sling.xss.ProtectionContext;
import org.apache.sling.xss.XSSAPI;
import org.apache.sling.xss.XSSFilter;
import org.owasp.encoder.Encode;
import org.owasp.esapi.ESAPI;
import org.owasp.esapi.Validator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.InputSource;
+import org.xml.sax.XMLReader;
@Component
@Service(value = XSSAPI.class)
public class XSSAPIImpl implements XSSAPI {
-
- // =============================================================================================
- // VALIDATORS
- //
+ private static final Logger LOGGER = LoggerFactory.getLogger(XSSAPIImpl.class);
@Reference
private XSSFilter xssFilter = null;
@@ -46,9 +57,30 @@ public class XSSAPIImpl implements XSSAP
private static final Pattern PATTERN_AUTO_DIMENSION = Pattern.compile("['\"]?auto['\"]?");
+ private SAXParserFactory factory;
+
+ @Activate
+ @SuppressWarnings("unused")
+ protected void activate() {
+ factory = SAXParserFactory.newInstance();
+ factory.setValidating(false);
+ factory.setNamespaceAware(true);
+ }
+
+ @Deactivate
+ @SuppressWarnings("unused")
+ protected void deactivate() {
+ factory = null;
+ }
+
+ // =============================================================================================
+ // VALIDATORS
+ //
+
/**
* @see org.apache.sling.xss.XSSAPI#getValidInteger(String, int)
*/
+ @Override
public Integer getValidInteger(String integer, int defaultValue) {
if (integer != null && integer.length() > 0) {
try {
@@ -65,6 +97,7 @@ public class XSSAPIImpl implements XSSAP
/**
* @see org.apache.sling.xss.XSSAPI#getValidLong(String, long)
*/
+ @Override
public Long getValidLong(String source, long defaultValue) {
if (source != null && source.length() > 0) {
try {
@@ -83,6 +116,7 @@ public class XSSAPIImpl implements XSSAP
/**
* @see org.apache.sling.xss.XSSAPI#getValidDimension(String, String)
*/
+ @Override
public String getValidDimension(String dimension, String defaultValue) {
if (dimension != null && dimension.length() > 0) {
if (PATTERN_AUTO_DIMENSION.matcher(dimension).matches()) {
@@ -156,6 +190,8 @@ public class XSSAPIImpl implements XSSAP
/**
* @see org.apache.sling.xss.XSSAPI#getValidHref(String)
*/
+ @Override
+ @Nonnull
public String getValidHref(final String url) {
if (url != null && url.length() > 0) {
// Percent-encode characters that are not allowed in unquoted
@@ -189,6 +225,7 @@ public class XSSAPIImpl implements XSSAP
/**
* @see org.apache.sling.xss.XSSAPI#getValidJSToken(String, String)
*/
+ @Override
public String getValidJSToken(String token, String defaultValue) {
if (token != null && token.length() > 0) {
token = token.trim();
@@ -238,6 +275,7 @@ public class XSSAPIImpl implements XSSAP
/**
* @see org.apache.sling.xss.XSSAPI#getValidStyleToken(String, String)
*/
+ @Override
public String getValidStyleToken(String token, String defaultValue) {
if (token != null && token.length() > 0 && token.matches(CSS_TOKEN)) {
return token;
@@ -249,6 +287,7 @@ public class XSSAPIImpl implements XSSAP
/**
* @see org.apache.sling.xss.XSSAPI#getValidCSSColor(String, String)
*/
+ @Override
public String getValidCSSColor(String color, String defaultColor) {
if (color != null && color.length() > 0) {
color = color.trim();
@@ -272,6 +311,7 @@ public class XSSAPIImpl implements XSSAP
/**
* @see org.apache.sling.xss.XSSAPI#getValidMultiLineComment(String, String)
*/
+ @Override
public String getValidMultiLineComment(String comment, String defaultComment) {
if (comment != null && !comment.contains("*/")) {
return comment;
@@ -279,6 +319,62 @@ public class XSSAPIImpl implements XSSAP
return defaultComment;
}
+ /**
+ * @see org.apache.sling.xss.XSSAPI#getValidJSON(String, String)
+ */
+ @Override
+ public String getValidJSON(String json, String defaultJson) {
+ if (json == null) {
+ return getValidJSON(defaultJson, "");
+ }
+ json = json.trim();
+ if ("".equals(json)) {
+ return "";
+ }
+ int curlyIx = json.indexOf("{");
+ int straightIx = json.indexOf("[");
+ if (curlyIx >= 0 && (curlyIx < straightIx || straightIx < 0)) {
+ try {
+ JSONObject obj = new JSONObject(json);
+ return obj.toString();
+ } catch (JSONException e) {
+ LOGGER.debug("JSON validation failed: " + e.getMessage(), e);
+ }
+ } else {
+ try {
+ JSONArray arr = new JSONArray(json);
+ return arr.toString();
+ } catch (JSONException e) {
+ LOGGER.debug("JSON validation failed: " + e.getMessage(), e);
+ }
+ }
+ return getValidJSON(defaultJson, "");
+ }
+
+ /**
+ * @see org.apache.sling.xss.XSSAPI#getValidXML(String, String)
+ */
+ @Override
+ public String getValidXML(String xml, String defaultXml) {
+ if (xml == null) {
+ return getValidXML(defaultXml, "");
+ }
+ xml = xml.trim();
+ if ("".equals(xml)) {
+ return "";
+ }
+
+ try {
+ SAXParser parser = factory.newSAXParser();
+ XMLReader reader = parser.getXMLReader();
+ reader.parse(new InputSource(new StringReader(xml)));
+ return xml;
+ } catch (Exception e) {
+ LOGGER.debug("XML validation failed: " + e.getMessage(), e);
+ }
+ return getValidXML(defaultXml, "");
+ }
+
// =============================================================================================
// ENCODERS
//
@@ -286,6 +382,7 @@ public class XSSAPIImpl implements XSSAP
/**
* @see org.apache.sling.xss.XSSAPI#encodeForHTML(String)
*/
+ @Override
public String encodeForHTML(String source) {
return source == null ? null : Encode.forHtml(source);
}
@@ -293,6 +390,7 @@ public class XSSAPIImpl implements XSSAP
/**
* @see org.apache.sling.xss.XSSAPI#encodeForHTMLAttr(String)
*/
+ @Override
public String encodeForHTMLAttr(String source) {
return source == null ? null : Encode.forHtmlAttribute(source);
}
@@ -300,6 +398,7 @@ public class XSSAPIImpl implements XSSAP
/**
* @see org.apache.sling.xss.XSSAPI#encodeForXML(String)
*/
+ @Override
public String encodeForXML(String source) {
return source == null ? null : Encode.forXml(source);
}
@@ -307,6 +406,7 @@ public class XSSAPIImpl implements XSSAP
/**
* @see org.apache.sling.xss.XSSAPI#encodeForXMLAttr(String)
*/
+ @Override
public String encodeForXMLAttr(String source) {
return source == null ? null : Encode.forXmlAttribute(source);
}
@@ -314,6 +414,7 @@ public class XSSAPIImpl implements XSSAP
/**
* @see org.apache.sling.xss.XSSAPI#encodeForJSString(String)
*/
+ @Override
public String encodeForJSString(String source) {
return source == null ? null : Encode.forJavaScript(source);
}
@@ -321,6 +422,7 @@ public class XSSAPIImpl implements XSSAP
/**
* @see org.apache.sling.xss.XSSAPI#encodeForCSSString(String)
*/
+ @Override
public String encodeForCSSString(String source) {
return source == null ? null : Encode.forCssString(source);
}
@@ -332,6 +434,8 @@ public class XSSAPIImpl implements XSSAP
/**
* @see org.apache.sling.xss.XSSAPI#filterHTML(String)
*/
+ @Override
+ @Nonnull
public String filterHTML(String source) {
return xssFilter.filter(ProtectionContext.HTML_HTML_CONTENT, source);
}
@@ -343,6 +447,7 @@ public class XSSAPIImpl implements XSSAP
/**
* @see org.apache.sling.xss.XSSAPI#getRequestSpecificAPI(org.apache.sling.api.SlingHttpServletRequest)
*/
+ @Override
public XSSAPI getRequestSpecificAPI(final SlingHttpServletRequest request) {
return this;
}
@@ -350,6 +455,7 @@ public class XSSAPIImpl implements XSSAP
/**
* @see org.apache.sling.xss.XSSAPI#getResourceResolverSpecificAPI(org.apache.sling.api.resource.ResourceResolver)
*/
+ @Override
public XSSAPI getResourceResolverSpecificAPI(final ResourceResolver resourceResolver) {
return this;
}
Modified: sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/package-info.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/package-info.java?rev=1679207&r1=1679206&r2=1679207&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/package-info.java (original)
+++ sling/trunk/bundles/extensions/xss/src/main/java/org/apache/sling/xss/package-info.java Wed May 13 13:40:07 2015
@@ -17,9 +17,9 @@
/**
* XSS Protection Service
*
- * @version 1.0.0
+ * @version 1.1.0
*/
-@Version("1.0.0")
+@Version("1.1.0")
package org.apache.sling.xss;
import aQute.bnd.annotation.Version;
Modified: sling/trunk/bundles/extensions/xss/src/test/java/org/apache/sling/xss/impl/XSSAPIImplTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/xss/src/test/java/org/apache/sling/xss/impl/XSSAPIImplTest.java?rev=1679207&r1=1679206&r2=1679207&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/xss/src/test/java/org/apache/sling/xss/impl/XSSAPIImplTest.java (original)
+++ sling/trunk/bundles/extensions/xss/src/test/java/org/apache/sling/xss/impl/XSSAPIImplTest.java Wed May 13 13:40:07 2015
@@ -41,6 +41,8 @@ import static org.mockito.Mockito.when;
public class XSSAPIImplTest {
public static final String RUBBISH = "rubbish";
+ public static final String RUBBISH_JSON = "[\"rubbish\"]";
+ public static final String RUBBISH_XML = "<rubbish/>";
private XSSAPI xssAPI;
@@ -59,6 +61,7 @@ public class XSSAPIImplTest {
Whitebox.setInternalState(xssFilter, "defaultHandler", mockPolicyHandler);
xssAPI = new XSSAPIImpl();
+ Whitebox.invokeMethod(xssAPI, "activate");
Field filterField = XSSAPIImpl.class.getDeclaredField("xssFilter");
filterField.setAccessible(true);
filterField.set(xssAPI, xssFilter);
@@ -550,4 +553,92 @@ public class XSSAPIImplTest {
}
}
}
+
+ @Test
+ public void testGetValidJSON() {
+ String[][] testData = {
+ {null, RUBBISH_JSON},
+ {"", ""},
+ {"1]", RUBBISH_JSON},
+ {"{}", "{}"},
+ {"{1}", RUBBISH_JSON},
+ {
+ "{test: 'test'}",
+ "{\"test\":\"test\"}"
+ },
+ {
+ "{test:\"test}",
+ RUBBISH_JSON
+ },
+ {
+ "{test1:'test1', test2: {test21: 'test21', test22: 'test22'}}",
+ "{\"test1\":\"test1\",\"test2\":{\"test21\":\"test21\",\"test22\":\"test22\"}}"
+ },
+ {"[]", "[]"},
+ {"[1,2]", "[1,2]"},
+ {"[1", RUBBISH_JSON},
+ {
+ "[{test: 'test'}]",
+ "[{\"test\":\"test\"}]"
+ }
+ };
+ for (String[] aTestData : testData) {
+ String source = aTestData[0];
+ String expected = aTestData[1];
+
+ String result = xssAPI.getValidJSON(source, RUBBISH_JSON);
+ if (!result.equals(expected)) {
+ fail("Validating JSON '" + source + "', expecting '" + expected + "', but got '" + result + "'");
+ }
+ }
+ }
+
+ @Test
+ public void testGetValidXML() {
+ String[][] testData = {
+ {null, RUBBISH_XML},
+ {"", ""},
+ {
+ "<t/>",
+ "<t/>"
+ },
+ {
+ "<t>",
+ RUBBISH_XML
+ },
+ {
+ "<t>test</t>",
+ "<t>test</t>"
+ },
+ {
+ "<t>test",
+ RUBBISH_XML
+ },
+ {
+ "<t t=\"t\">test</t>",
+ "<t t=\"t\">test</t>"
+ },
+ {
+ "<t t=\"t>test</t>",
+ RUBBISH_XML
+ },
+ {
+ "<t><w>xyz</w></t>",
+ "<t><w>xyz</w></t>"
+ },
+ {
+ "<t><w>xyz</t></w>",
+ RUBBISH_XML
+ }
+ };
+ for (String[] aTestData : testData) {
+ String source = aTestData[0];
+ String expected = aTestData[1];
+
+ String result = xssAPI.getValidXML(source, RUBBISH_XML);
+ if (!result.equals(expected)) {
+ fail("Validating XML '" + source + "', expecting '" + expected + "', but got '" + result + "'");
+ }
+ }
+ }
}