Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed issue #310 #541

Merged
merged 11 commits into from
Jul 2, 2020
7 changes: 7 additions & 0 deletions configuration/esapi/ESAPI.properties
Original file line number Diff line number Diff line change
Expand Up @@ -535,3 +535,10 @@ Validator.AcceptLenientDates=false
#
#Validator.HtmlValidationAction=clean
Validator.HtmlValidationAction=throw

# With the fix for #310 to enable loading antisamy-esapi.xml from the classpath
# also an enhancement was made to be able to use a different filename for the configuration.
# You don't have to configure the filename here, but in that case the code will keep looking for antisamy-esapi.xml.
# This is the default behaviour of ESAPI.
#
#Validator.HtmlValidationConfigurationFile=antisamy-esapi.xml
1 change: 0 additions & 1 deletion src/main/java/org/owasp/esapi/SecurityConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,6 @@ public interface SecurityConfiguration extends EsapiPropertyLoader {
*/
InputStream getResourceStream( String filename ) throws IOException;


/**
* Sets the ESAPI resource directory.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ public static SecurityConfiguration getInstance() {
public static final String VALIDATION_PROPERTIES_MULTIVALUED = "Validator.ConfigurationFile.MultiValued";
public static final String ACCEPT_LENIENT_DATES = "Validator.AcceptLenientDates";
public static final String VALIDATOR_HTML_VALIDATION_ACTION = "Validator.HtmlValidationAction";
public static final String VALIDATOR_HTML_VALIDATION_CONFIGURATION_FILE = "Validator.HtmlValidationConfigurationFile";

/**
* Special {@code System} property that, if set to {@code true}, will
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.owasp.validator.html.Policy;
import org.owasp.validator.html.PolicyException;
import org.owasp.validator.html.ScanException;
import org.owasp.esapi.reference.DefaultSecurityConfiguration;


/**
Expand All @@ -46,22 +47,113 @@ public class HTMLValidationRule extends StringValidationRule {
/** OWASP AntiSamy markup verification policy */
private static Policy antiSamyPolicy = null;
private static final Logger LOGGER = ESAPI.getLogger( "HTMLValidationRule" );
private static final String ANTISAMYPOLICY_FILENAME = "antisamy-esapi.xml";

/**
* Used to load antisamy-esapi.xml from a variety of different classpath locations.
* The classpath locations are the same classpath locations as used to load esapi.properties.
* See DefaultSecurityConfiguration.DefaultSearchPath.
*
* @param fileName The resource file filename.
*/
private static InputStream getResourceStreamFromClasspath(String fileName) {
InputStream resourceStream = null;

ClassLoader[] loaders = new ClassLoader[] {
Thread.currentThread().getContextClassLoader(),
ClassLoader.getSystemClassLoader(),
ESAPI.securityConfiguration().getClass().getClassLoader()
/* can't use just getClass.getClassLoader() in a static context, so using the DefaultSecurityConfiguration class. */
};

String[] classLoaderNames = {
"current thread context class loader",
"system class loader",
"class loader for DefaultSecurityConfiguration class"
};

int i = 0;
for (ClassLoader loader : loaders) {
// try root
String currentClasspathSearchLocation = "/ (root)";
resourceStream = loader.getResourceAsStream(DefaultSecurityConfiguration.DefaultSearchPath.ROOT.value() + fileName);

// try resourceDirectory folder
if (resourceStream == null){
currentClasspathSearchLocation = DefaultSecurityConfiguration.DefaultSearchPath.RESOURCE_DIRECTORY.value();
resourceStream = loader.getResourceAsStream(currentClasspathSearchLocation + fileName);
}

// try .esapi folder. Look here first for backward compatibility.
if (resourceStream == null){
currentClasspathSearchLocation = DefaultSecurityConfiguration.DefaultSearchPath.DOT_ESAPI.value();
resourceStream = loader.getResourceAsStream(currentClasspathSearchLocation + fileName);
}

// try esapi folder (new directory)
if (resourceStream == null){
currentClasspathSearchLocation = DefaultSecurityConfiguration.DefaultSearchPath.ESAPI.value();
resourceStream = loader.getResourceAsStream(currentClasspathSearchLocation + fileName);
}

// try resources folder
if (resourceStream == null){
currentClasspathSearchLocation = DefaultSecurityConfiguration.DefaultSearchPath.RESOURCES.value();
resourceStream = loader.getResourceAsStream(currentClasspathSearchLocation + fileName);
}

// try src/main/resources folder
if (resourceStream == null){
currentClasspathSearchLocation = DefaultSecurityConfiguration.DefaultSearchPath.SRC_MAIN_RESOURCES.value();
resourceStream = loader.getResourceAsStream(currentClasspathSearchLocation + fileName);
}

if (resourceStream != null) {
LOGGER.info(Logger.EVENT_FAILURE, "SUCCESSFULLY LOADED " + fileName + " via the CLASSPATH from '" +
currentClasspathSearchLocation + "' using " + classLoaderNames[i] + "!");
break; // Outta here since we've found and loaded it.
}

i++;
}

return resourceStream;
}

static {
InputStream resourceStream = null;
String antisamyPolicyFilename = null;

try {
antisamyPolicyFilename = ESAPI.securityConfiguration().getStringProp(
// Future: This will be moved to a new PropNames class
org.owasp.esapi.reference.DefaultSecurityConfiguration.VALIDATOR_HTML_VALIDATION_CONFIGURATION_FILE );
} catch (ConfigurationException cex) {

LOGGER.info(Logger.EVENT_FAILURE, "ESAPI property " +
org.owasp.esapi.reference.DefaultSecurityConfiguration.VALIDATOR_HTML_VALIDATION_CONFIGURATION_FILE +
" not set, using default value: " + ANTISAMYPOLICY_FILENAME);
antisamyPolicyFilename = ANTISAMYPOLICY_FILENAME;
}
try {
resourceStream = ESAPI.securityConfiguration().getResourceStream("antisamy-esapi.xml");
resourceStream = ESAPI.securityConfiguration().getResourceStream(antisamyPolicyFilename);
} catch (IOException e) {
throw new ConfigurationException("Couldn't find antisamy-esapi.xml", e);
}

LOGGER.info(Logger.EVENT_FAILURE, "Loading " + antisamyPolicyFilename + " from classpaths");

resourceStream = getResourceStreamFromClasspath(antisamyPolicyFilename);
}
if (resourceStream != null) {
try {
antiSamyPolicy = Policy.getInstance(resourceStream);
} catch (PolicyException e) {
throw new ConfigurationException("Couldn't parse antisamy policy", e);
}
}
throw new ConfigurationException("Couldn't parse " + antisamyPolicyFilename, e);
}
}
else {
throw new ConfigurationException("Couldn't find " + antisamyPolicyFilename);
}
}

public HTMLValidationRule( String typeName ) {
super( typeName );
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/**
* OWASP Enterprise Security API (ESAPI)
*
* This file is part of the Open Web Application Security Project (OWASP)
* Enterprise Security API (ESAPI) project. For details, please see
* <a href="http://www.owasp.org/index.php/ESAPI">http://www.owasp.org/index.php/ESAPI</a>.
*
* Copyright (c) 2019 - The OWASP Foundation
*
* The ESAPI is published by OWASP under the BSD license. You should read and accept the
* LICENSE before you use, modify, and/or redistribute this software.
*
* @author [email protected]
* @since 2019
*/
package org.owasp.esapi.reference.validation;

import org.owasp.esapi.ESAPI;
import org.owasp.esapi.SecurityConfiguration;
import org.owasp.esapi.SecurityConfigurationWrapper;
import org.owasp.esapi.ValidationErrorList;
import org.owasp.esapi.ValidationRule;
import org.owasp.esapi.Validator;
import org.owasp.esapi.errors.ValidationException;
import org.owasp.esapi.reference.validation.HTMLValidationRule;

import org.junit.Test;
import org.junit.Before;
import org.junit.After;
import org.junit.Rule;
import org.junit.rules.ExpectedException;
import static org.junit.Assert.*;

/**
* The Class HTMLValidationRuleThrowsTest.
*
* Based on original test cases, testGetValidSafeHTML() and
* testIsValidSafeHTML() from ValidatorTest by
* Mike Fauzy ([email protected]) and
* Jeff Williams ([email protected])
* that were originally part of src/test/java/org/owasp/esapi/reference/ValidatorTest.java.
*
* This class tests the cases where the new ESAPI.property
* Validator.HtmlValidationAction
* is set to "throw", which causes certain calls to
* ESAPI.validator().getValidSafeHTML() or ESAPI.validator().isValidSafeHTML()
* to throw a ValidationException rather than simply logging a warning and returning
* the cleansed (sanitizied) output when certain unsafe input is encountered.
*/
public class HTMLValidationRuleClasspathTest {
private static class ConfOverride extends SecurityConfigurationWrapper {
private String desiredReturnAction = "clean";
private String desiredReturnConfigurationFile = "antisamy-esapi.xml";

ConfOverride(SecurityConfiguration orig, String desiredReturnAction, String desiredReturnConfigurationFile) {
super(orig);
this.desiredReturnAction = desiredReturnAction;
this.desiredReturnConfigurationFile = desiredReturnConfigurationFile;
}

@Override
public String getStringProp(String propName) {
// Would it be better making this file a static import?
if ( propName.equals( org.owasp.esapi.reference.DefaultSecurityConfiguration.VALIDATOR_HTML_VALIDATION_ACTION ) ) {
return desiredReturnAction;
} else if ( propName.equals( org.owasp.esapi.reference.DefaultSecurityConfiguration.VALIDATOR_HTML_VALIDATION_CONFIGURATION_FILE ) ) {
return desiredReturnConfigurationFile;
} else {
return super.getStringProp( propName );
}
}
}

// Must be public!
@Rule
public ExpectedException thrownEx = ExpectedException.none();

@After
public void tearDown() throws Exception {
ESAPI.override(null);
thrownEx = ExpectedException.none();
}

@Before
public void setUp() throws Exception {
ESAPI.override(
new ConfOverride( ESAPI.securityConfiguration(), "throw", "antisamy-esapi-CP.xml" )
);

}

@Test
public void testGetValid() throws Exception {
System.out.println("getValidCP");
Validator instance = ESAPI.validator();
HTMLValidationRule rule = new HTMLValidationRule("testCP");
ESAPI.validator().addRule(rule);

thrownEx.expect(ValidationException.class);
thrownEx.expectMessage("test: Invalid HTML input");

instance.getRule("testCP").getValid("test", "Test. <script>alert(document.cookie)</script>");
}

@Test
public void testGetValidSafeHTML() throws Exception {
System.out.println("getValidSafeHTML");
Validator instance = ESAPI.validator();

HTMLValidationRule rule = new HTMLValidationRule("test");
ESAPI.validator().addRule(rule);

String[] testInput = {
// These first two don't cause AntiSamy to throw.
// "Test. <a href=\"http://www.aspectsecurity.com\">Aspect Security</a>",
// "Test. <<div on<script></script>load=alert()",
"Test. <script>alert(document.cookie)</script>",
"Test. <script>alert(document.cookie)</script>",
"Test. <div style={xss:expression(xss)}>b</div>",
"Test. <s%00cript>alert(document.cookie)</script>",
"Test. <s\tcript>alert(document.cookie)</script>",
"Test. <s\tcript>alert(document.cookie)</script>"
};

int errors = 0;
for( int i = 0; i < testInput.length; i++ ) {
try {
String result = instance.getValidSafeHTML("test", testInput[i], 100, false);
errors++;
System.out.println("testGetValidSafeHTML(): testInput '" + testInput[i] + "' failed to throw.");
}
catch( ValidationException vex ) {
System.out.println("testGetValidSafeHTML(): testInput '" + testInput[i] + "' returned:");
System.out.println("\t" + i + ": logMsg =" + vex.getLogMessage());
assertEquals( vex.getUserMessage(), "test: Invalid HTML input");
}
catch( Exception ex ) {
errors++;
System.out.println("testGetValidSafeHTML(): testInput '" + testInput[i] +
"' threw wrong exception type: " + ex.getClass().getName() );
}
}

if ( errors > 0 ) {
fail("testGetValidSafeHTML() encountered " + errors + " failures.");
}
}

@Test
public void testIsValidSafeHTML() {
System.out.println("isValidSafeHTML");
Validator instance = ESAPI.validator();
thrownEx = ExpectedException.none(); // Not expecting any exceptions here.

assertTrue(instance.isValidSafeHTML("test", "<b>Jeff</b>", 100, false));
assertTrue(instance.isValidSafeHTML("test", "<a href=\"http://www.aspectsecurity.com\">Aspect Security</a>", 100, false));
assertFalse(instance.isValidSafeHTML("test", "Test. <script>alert(document.cookie)</script>", 100, false));
assertFalse(instance.isValidSafeHTML("test", "Test. <div style={xss:expression(xss)}>", 100, false));
assertFalse(instance.isValidSafeHTML("test", "Test. <s%00cript>alert(document.cookie)</script>", 100, false));
assertFalse(instance.isValidSafeHTML("test", "Test. <s\tcript>alert(document.cookie)</script>", 100, false));
assertFalse(instance.isValidSafeHTML("test", "Test. <s\r\n\0cript>alert(document.cookie)</script>", 100, false));

ValidationErrorList errors = new ValidationErrorList();
assertFalse(instance.isValidSafeHTML("test1", "Test. <script>alert(document.cookie)</script>", 100, false, errors));
assertFalse(instance.isValidSafeHTML("test2", "Test. <div style={xss:expression(xss)}>", 100, false, errors));
assertFalse(instance.isValidSafeHTML("test3", "Test. <s%00cript>alert(document.cookie)</script>", 100, false, errors));
assertFalse(instance.isValidSafeHTML("test4", "Test. <s\tcript>alert(document.cookie)</script>", 100, false, errors));
assertFalse(instance.isValidSafeHTML("test5", "Test. <s\r\n\0cript>alert(document.cookie)</script>", 100, false, errors));
assertTrue( errors.size() == 5 );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* @author [email protected]
* @since 2019
*/
package org.owasp.esapi.reference;
package org.owasp.esapi.reference.validation;

import org.owasp.esapi.ESAPI;
import org.owasp.esapi.EncoderConstants;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* @author [email protected]
* @since 2019
*/
package org.owasp.esapi.reference;
package org.owasp.esapi.reference.validation;
kwwall marked this conversation as resolved.
Show resolved Hide resolved

import org.owasp.esapi.ESAPI;
import org.owasp.esapi.SecurityConfiguration;
Expand Down
Loading