Skip to content

Commit f00db32

Browse files
authored
Fixed issue #310 (#541)
Fix #310 * issue-#310-coding-done * issue-#310-fixed-testclases-package * issue-ESAPI#310-tested-and-cleaned-code * issue-ESAPI#310-removed-an-useless-import * issue-#310-fixed-review-comments * issue-ESAPI#310-filename-configurable-and-added-testclass * Updated-ESAPI.properties-with-comment
1 parent 4821948 commit f00db32

File tree

8 files changed

+771
-9
lines changed

8 files changed

+771
-9
lines changed

configuration/esapi/ESAPI.properties

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,3 +535,10 @@ Validator.AcceptLenientDates=false
535535
#
536536
#Validator.HtmlValidationAction=clean
537537
Validator.HtmlValidationAction=throw
538+
539+
# With the fix for #310 to enable loading antisamy-esapi.xml from the classpath
540+
# also an enhancement was made to be able to use a different filename for the configuration.
541+
# You don't have to configure the filename here, but in that case the code will keep looking for antisamy-esapi.xml.
542+
# This is the default behaviour of ESAPI.
543+
#
544+
#Validator.HtmlValidationConfigurationFile=antisamy-esapi.xml

src/main/java/org/owasp/esapi/SecurityConfiguration.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -640,7 +640,6 @@ public interface SecurityConfiguration extends EsapiPropertyLoader {
640640
*/
641641
InputStream getResourceStream( String filename ) throws IOException;
642642

643-
644643
/**
645644
* Sets the ESAPI resource directory.
646645
*

src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ public static SecurityConfiguration getInstance() {
159159
public static final String VALIDATION_PROPERTIES_MULTIVALUED = "Validator.ConfigurationFile.MultiValued";
160160
public static final String ACCEPT_LENIENT_DATES = "Validator.AcceptLenientDates";
161161
public static final String VALIDATOR_HTML_VALIDATION_ACTION = "Validator.HtmlValidationAction";
162+
public static final String VALIDATOR_HTML_VALIDATION_CONFIGURATION_FILE = "Validator.HtmlValidationConfigurationFile";
162163

163164
/**
164165
* Special {@code System} property that, if set to {@code true}, will

src/main/java/org/owasp/esapi/reference/validation/HTMLValidationRule.java

Lines changed: 98 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.owasp.validator.html.Policy;
3131
import org.owasp.validator.html.PolicyException;
3232
import org.owasp.validator.html.ScanException;
33+
import org.owasp.esapi.reference.DefaultSecurityConfiguration;
3334

3435

3536
/**
@@ -46,22 +47,113 @@ public class HTMLValidationRule extends StringValidationRule {
4647
/** OWASP AntiSamy markup verification policy */
4748
private static Policy antiSamyPolicy = null;
4849
private static final Logger LOGGER = ESAPI.getLogger( "HTMLValidationRule" );
50+
private static final String ANTISAMYPOLICY_FILENAME = "antisamy-esapi.xml";
51+
52+
/**
53+
* Used to load antisamy-esapi.xml from a variety of different classpath locations.
54+
* The classpath locations are the same classpath locations as used to load esapi.properties.
55+
* See DefaultSecurityConfiguration.DefaultSearchPath.
56+
*
57+
* @param fileName The resource file filename.
58+
*/
59+
private static InputStream getResourceStreamFromClasspath(String fileName) {
60+
InputStream resourceStream = null;
61+
62+
ClassLoader[] loaders = new ClassLoader[] {
63+
Thread.currentThread().getContextClassLoader(),
64+
ClassLoader.getSystemClassLoader(),
65+
ESAPI.securityConfiguration().getClass().getClassLoader()
66+
/* can't use just getClass.getClassLoader() in a static context, so using the DefaultSecurityConfiguration class. */
67+
};
68+
69+
String[] classLoaderNames = {
70+
"current thread context class loader",
71+
"system class loader",
72+
"class loader for DefaultSecurityConfiguration class"
73+
};
74+
75+
int i = 0;
76+
for (ClassLoader loader : loaders) {
77+
// try root
78+
String currentClasspathSearchLocation = "/ (root)";
79+
resourceStream = loader.getResourceAsStream(DefaultSecurityConfiguration.DefaultSearchPath.ROOT.value() + fileName);
80+
81+
// try resourceDirectory folder
82+
if (resourceStream == null){
83+
currentClasspathSearchLocation = DefaultSecurityConfiguration.DefaultSearchPath.RESOURCE_DIRECTORY.value();
84+
resourceStream = loader.getResourceAsStream(currentClasspathSearchLocation + fileName);
85+
}
86+
87+
// try .esapi folder. Look here first for backward compatibility.
88+
if (resourceStream == null){
89+
currentClasspathSearchLocation = DefaultSecurityConfiguration.DefaultSearchPath.DOT_ESAPI.value();
90+
resourceStream = loader.getResourceAsStream(currentClasspathSearchLocation + fileName);
91+
}
92+
93+
// try esapi folder (new directory)
94+
if (resourceStream == null){
95+
currentClasspathSearchLocation = DefaultSecurityConfiguration.DefaultSearchPath.ESAPI.value();
96+
resourceStream = loader.getResourceAsStream(currentClasspathSearchLocation + fileName);
97+
}
98+
99+
// try resources folder
100+
if (resourceStream == null){
101+
currentClasspathSearchLocation = DefaultSecurityConfiguration.DefaultSearchPath.RESOURCES.value();
102+
resourceStream = loader.getResourceAsStream(currentClasspathSearchLocation + fileName);
103+
}
104+
105+
// try src/main/resources folder
106+
if (resourceStream == null){
107+
currentClasspathSearchLocation = DefaultSecurityConfiguration.DefaultSearchPath.SRC_MAIN_RESOURCES.value();
108+
resourceStream = loader.getResourceAsStream(currentClasspathSearchLocation + fileName);
109+
}
110+
111+
if (resourceStream != null) {
112+
LOGGER.info(Logger.EVENT_FAILURE, "SUCCESSFULLY LOADED " + fileName + " via the CLASSPATH from '" +
113+
currentClasspathSearchLocation + "' using " + classLoaderNames[i] + "!");
114+
break; // Outta here since we've found and loaded it.
115+
}
116+
117+
i++;
118+
}
119+
120+
return resourceStream;
121+
}
49122

50123
static {
51124
InputStream resourceStream = null;
125+
String antisamyPolicyFilename = null;
126+
127+
try {
128+
antisamyPolicyFilename = ESAPI.securityConfiguration().getStringProp(
129+
// Future: This will be moved to a new PropNames class
130+
org.owasp.esapi.reference.DefaultSecurityConfiguration.VALIDATOR_HTML_VALIDATION_CONFIGURATION_FILE );
131+
} catch (ConfigurationException cex) {
132+
133+
LOGGER.info(Logger.EVENT_FAILURE, "ESAPI property " +
134+
org.owasp.esapi.reference.DefaultSecurityConfiguration.VALIDATOR_HTML_VALIDATION_CONFIGURATION_FILE +
135+
" not set, using default value: " + ANTISAMYPOLICY_FILENAME);
136+
antisamyPolicyFilename = ANTISAMYPOLICY_FILENAME;
137+
}
52138
try {
53-
resourceStream = ESAPI.securityConfiguration().getResourceStream("antisamy-esapi.xml");
139+
resourceStream = ESAPI.securityConfiguration().getResourceStream(antisamyPolicyFilename);
54140
} catch (IOException e) {
55-
throw new ConfigurationException("Couldn't find antisamy-esapi.xml", e);
56-
}
141+
142+
LOGGER.info(Logger.EVENT_FAILURE, "Loading " + antisamyPolicyFilename + " from classpaths");
143+
144+
resourceStream = getResourceStreamFromClasspath(antisamyPolicyFilename);
145+
}
57146
if (resourceStream != null) {
58147
try {
59148
antiSamyPolicy = Policy.getInstance(resourceStream);
60149
} catch (PolicyException e) {
61-
throw new ConfigurationException("Couldn't parse antisamy policy", e);
62-
}
63-
}
150+
throw new ConfigurationException("Couldn't parse " + antisamyPolicyFilename, e);
151+
}
64152
}
153+
else {
154+
throw new ConfigurationException("Couldn't find " + antisamyPolicyFilename);
155+
}
156+
}
65157

66158
public HTMLValidationRule( String typeName ) {
67159
super( typeName );
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/**
2+
* OWASP Enterprise Security API (ESAPI)
3+
*
4+
* This file is part of the Open Web Application Security Project (OWASP)
5+
* Enterprise Security API (ESAPI) project. For details, please see
6+
* <a href="http://www.owasp.org/index.php/ESAPI">http://www.owasp.org/index.php/ESAPI</a>.
7+
*
8+
* Copyright (c) 2019 - The OWASP Foundation
9+
*
10+
* The ESAPI is published by OWASP under the BSD license. You should read and accept the
11+
* LICENSE before you use, modify, and/or redistribute this software.
12+
*
13+
14+
* @since 2019
15+
*/
16+
package org.owasp.esapi.reference.validation;
17+
18+
import org.owasp.esapi.ESAPI;
19+
import org.owasp.esapi.SecurityConfiguration;
20+
import org.owasp.esapi.SecurityConfigurationWrapper;
21+
import org.owasp.esapi.ValidationErrorList;
22+
import org.owasp.esapi.ValidationRule;
23+
import org.owasp.esapi.Validator;
24+
import org.owasp.esapi.errors.ValidationException;
25+
import org.owasp.esapi.reference.validation.HTMLValidationRule;
26+
27+
import org.junit.Test;
28+
import org.junit.Before;
29+
import org.junit.After;
30+
import org.junit.Rule;
31+
import org.junit.rules.ExpectedException;
32+
import static org.junit.Assert.*;
33+
34+
/**
35+
* The Class HTMLValidationRuleThrowsTest.
36+
*
37+
* Based on original test cases, testGetValidSafeHTML() and
38+
* testIsValidSafeHTML() from ValidatorTest by
39+
* Mike Fauzy ([email protected]) and
40+
* Jeff Williams ([email protected])
41+
* that were originally part of src/test/java/org/owasp/esapi/reference/ValidatorTest.java.
42+
*
43+
* This class tests the cases where the new ESAPI.property
44+
* Validator.HtmlValidationAction
45+
* is set to "throw", which causes certain calls to
46+
* ESAPI.validator().getValidSafeHTML() or ESAPI.validator().isValidSafeHTML()
47+
* to throw a ValidationException rather than simply logging a warning and returning
48+
* the cleansed (sanitizied) output when certain unsafe input is encountered.
49+
*/
50+
public class HTMLValidationRuleClasspathTest {
51+
private static class ConfOverride extends SecurityConfigurationWrapper {
52+
private String desiredReturnAction = "clean";
53+
private String desiredReturnConfigurationFile = "antisamy-esapi.xml";
54+
55+
ConfOverride(SecurityConfiguration orig, String desiredReturnAction, String desiredReturnConfigurationFile) {
56+
super(orig);
57+
this.desiredReturnAction = desiredReturnAction;
58+
this.desiredReturnConfigurationFile = desiredReturnConfigurationFile;
59+
}
60+
61+
@Override
62+
public String getStringProp(String propName) {
63+
// Would it be better making this file a static import?
64+
if ( propName.equals( org.owasp.esapi.reference.DefaultSecurityConfiguration.VALIDATOR_HTML_VALIDATION_ACTION ) ) {
65+
return desiredReturnAction;
66+
} else if ( propName.equals( org.owasp.esapi.reference.DefaultSecurityConfiguration.VALIDATOR_HTML_VALIDATION_CONFIGURATION_FILE ) ) {
67+
return desiredReturnConfigurationFile;
68+
} else {
69+
return super.getStringProp( propName );
70+
}
71+
}
72+
}
73+
74+
// Must be public!
75+
@Rule
76+
public ExpectedException thrownEx = ExpectedException.none();
77+
78+
@After
79+
public void tearDown() throws Exception {
80+
ESAPI.override(null);
81+
thrownEx = ExpectedException.none();
82+
}
83+
84+
@Before
85+
public void setUp() throws Exception {
86+
ESAPI.override(
87+
new ConfOverride( ESAPI.securityConfiguration(), "throw", "antisamy-esapi-CP.xml" )
88+
);
89+
90+
}
91+
92+
@Test
93+
public void testGetValid() throws Exception {
94+
System.out.println("getValidCP");
95+
Validator instance = ESAPI.validator();
96+
HTMLValidationRule rule = new HTMLValidationRule("testCP");
97+
ESAPI.validator().addRule(rule);
98+
99+
thrownEx.expect(ValidationException.class);
100+
thrownEx.expectMessage("test: Invalid HTML input");
101+
102+
instance.getRule("testCP").getValid("test", "Test. <script>alert(document.cookie)</script>");
103+
}
104+
105+
@Test
106+
public void testGetValidSafeHTML() throws Exception {
107+
System.out.println("getValidSafeHTML");
108+
Validator instance = ESAPI.validator();
109+
110+
HTMLValidationRule rule = new HTMLValidationRule("test");
111+
ESAPI.validator().addRule(rule);
112+
113+
String[] testInput = {
114+
// These first two don't cause AntiSamy to throw.
115+
// "Test. <a href=\"http://www.aspectsecurity.com\">Aspect Security</a>",
116+
// "Test. <<div on<script></script>load=alert()",
117+
"Test. <script>alert(document.cookie)</script>",
118+
"Test. <script>alert(document.cookie)</script>",
119+
"Test. <div style={xss:expression(xss)}>b</div>",
120+
"Test. <s%00cript>alert(document.cookie)</script>",
121+
"Test. <s\tcript>alert(document.cookie)</script>",
122+
"Test. <s\tcript>alert(document.cookie)</script>"
123+
};
124+
125+
int errors = 0;
126+
for( int i = 0; i < testInput.length; i++ ) {
127+
try {
128+
String result = instance.getValidSafeHTML("test", testInput[i], 100, false);
129+
errors++;
130+
System.out.println("testGetValidSafeHTML(): testInput '" + testInput[i] + "' failed to throw.");
131+
}
132+
catch( ValidationException vex ) {
133+
System.out.println("testGetValidSafeHTML(): testInput '" + testInput[i] + "' returned:");
134+
System.out.println("\t" + i + ": logMsg =" + vex.getLogMessage());
135+
assertEquals( vex.getUserMessage(), "test: Invalid HTML input");
136+
}
137+
catch( Exception ex ) {
138+
errors++;
139+
System.out.println("testGetValidSafeHTML(): testInput '" + testInput[i] +
140+
"' threw wrong exception type: " + ex.getClass().getName() );
141+
}
142+
}
143+
144+
if ( errors > 0 ) {
145+
fail("testGetValidSafeHTML() encountered " + errors + " failures.");
146+
}
147+
}
148+
149+
@Test
150+
public void testIsValidSafeHTML() {
151+
System.out.println("isValidSafeHTML");
152+
Validator instance = ESAPI.validator();
153+
thrownEx = ExpectedException.none(); // Not expecting any exceptions here.
154+
155+
assertTrue(instance.isValidSafeHTML("test", "<b>Jeff</b>", 100, false));
156+
assertTrue(instance.isValidSafeHTML("test", "<a href=\"http://www.aspectsecurity.com\">Aspect Security</a>", 100, false));
157+
assertFalse(instance.isValidSafeHTML("test", "Test. <script>alert(document.cookie)</script>", 100, false));
158+
assertFalse(instance.isValidSafeHTML("test", "Test. <div style={xss:expression(xss)}>", 100, false));
159+
assertFalse(instance.isValidSafeHTML("test", "Test. <s%00cript>alert(document.cookie)</script>", 100, false));
160+
assertFalse(instance.isValidSafeHTML("test", "Test. <s\tcript>alert(document.cookie)</script>", 100, false));
161+
assertFalse(instance.isValidSafeHTML("test", "Test. <s\r\n\0cript>alert(document.cookie)</script>", 100, false));
162+
163+
ValidationErrorList errors = new ValidationErrorList();
164+
assertFalse(instance.isValidSafeHTML("test1", "Test. <script>alert(document.cookie)</script>", 100, false, errors));
165+
assertFalse(instance.isValidSafeHTML("test2", "Test. <div style={xss:expression(xss)}>", 100, false, errors));
166+
assertFalse(instance.isValidSafeHTML("test3", "Test. <s%00cript>alert(document.cookie)</script>", 100, false, errors));
167+
assertFalse(instance.isValidSafeHTML("test4", "Test. <s\tcript>alert(document.cookie)</script>", 100, false, errors));
168+
assertFalse(instance.isValidSafeHTML("test5", "Test. <s\r\n\0cript>alert(document.cookie)</script>", 100, false, errors));
169+
assertTrue( errors.size() == 5 );
170+
}
171+
}

src/test/java/org/owasp/esapi/reference/validation/HTMLValidationRuleCleanTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
1414
* @since 2019
1515
*/
16-
package org.owasp.esapi.reference;
16+
package org.owasp.esapi.reference.validation;
1717

1818
import org.owasp.esapi.ESAPI;
1919
import org.owasp.esapi.EncoderConstants;

src/test/java/org/owasp/esapi/reference/validation/HTMLValidationRuleThrowsTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
1414
* @since 2019
1515
*/
16-
package org.owasp.esapi.reference;
16+
package org.owasp.esapi.reference.validation;
1717

1818
import org.owasp.esapi.ESAPI;
1919
import org.owasp.esapi.SecurityConfiguration;

0 commit comments

Comments
 (0)