diff --git a/WebContent/WEB-INF/lib/java10-shim-20240325.1.jar b/WebContent/WEB-INF/lib/java10-shim-20240325.1.jar new file mode 100644 index 000000000..59c87d514 Binary files /dev/null and b/WebContent/WEB-INF/lib/java10-shim-20240325.1.jar differ diff --git a/WebContent/WEB-INF/lib/java8-shim-20240325.1.jar b/WebContent/WEB-INF/lib/java8-shim-20240325.1.jar new file mode 100644 index 000000000..d2e2bac67 Binary files /dev/null and b/WebContent/WEB-INF/lib/java8-shim-20240325.1.jar differ diff --git a/WebContent/WEB-INF/lib/owasp-java-html-sanitizer-20240325.1.jar b/WebContent/WEB-INF/lib/owasp-java-html-sanitizer-20240325.1.jar new file mode 100644 index 000000000..9becef68d Binary files /dev/null and b/WebContent/WEB-INF/lib/owasp-java-html-sanitizer-20240325.1.jar differ diff --git a/build.gradle b/build.gradle index 352a67a2a..e4678cfe1 100644 --- a/build.gradle +++ b/build.gradle @@ -243,6 +243,7 @@ test { includeTestsMatching "org.scada_lts.web.security.XssProtectHtmlEscapeUtilsTest" includeTestsMatching "org.scada_lts.web.security.XssUtilsTest" includeTestsMatching "org.scada_lts.web.mvc.api.validation.css.CssValidatorTestsSuite" + includeTestsMatching "org.scada_lts.web.security.XssUtilsTestsSuite" } failFast = true diff --git a/src/org/scada_lts/web/mvc/api/UserCommentAPI.java b/src/org/scada_lts/web/mvc/api/UserCommentAPI.java index a8260fe59..281d12c19 100644 --- a/src/org/scada_lts/web/mvc/api/UserCommentAPI.java +++ b/src/org/scada_lts/web/mvc/api/UserCommentAPI.java @@ -7,6 +7,7 @@ import org.scada_lts.mango.service.UserCommentService; import org.scada_lts.web.beans.ApplicationBeans; import org.scada_lts.web.mvc.api.json.JsonUserComment; +import org.scada_lts.web.security.XssValid; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; @@ -98,6 +99,8 @@ public ResponseEntity deleteUserComment(HttpServletRequest request, @Pat } static class CreateUserComment { + + @XssValid private String commentText; public CreateUserComment() { diff --git a/src/org/scada_lts/web/mvc/api/css/CssStyle.java b/src/org/scada_lts/web/mvc/api/css/CssStyle.java index 0021b3f7f..8c919ec8c 100644 --- a/src/org/scada_lts/web/mvc/api/css/CssStyle.java +++ b/src/org/scada_lts/web/mvc/api/css/CssStyle.java @@ -4,10 +4,11 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import org.scada_lts.web.beans.validation.css.CssValid; +import org.scada_lts.web.security.XssValid; public class CssStyle { - @CssValid + @CssValid @XssValid private final String content; @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) diff --git a/src/org/scada_lts/web/security/OwaspXssValidator.java b/src/org/scada_lts/web/security/OwaspXssValidator.java new file mode 100644 index 000000000..739221f3a --- /dev/null +++ b/src/org/scada_lts/web/security/OwaspXssValidator.java @@ -0,0 +1,36 @@ +package org.scada_lts.web.security; + +import org.scada_lts.web.beans.validation.ScadaValidator; +import org.owasp.html.HtmlPolicyBuilder; +import org.owasp.html.PolicyFactory; + +public class OwaspXssValidator implements ScadaValidator { + + private final PolicyFactory policyFactory; + + public OwaspXssValidator() { + this.policyFactory = createPolicyFactory(); + } + + @Override + public void validate(String input) throws XssValidatorException { + try { + validateXss(input); + } catch (Exception e) { + throw new XssValidatorException("XSS validation failed: " + e.getMessage(), e); + } + } + + void validateXss(String input) throws XssValidatorException { + String sanitized = policyFactory.sanitize(input); + if (!sanitized.equals(input)) { + throw new XssValidatorException("Potential XSS attack detected"); + } + } + + private PolicyFactory createPolicyFactory() { + return new HtmlPolicyBuilder() + .allowElements("b", "i", "u", "strong", "em", "p", "ul", "ol", "li") + .toFactory(); + } +} diff --git a/src/org/scada_lts/web/security/XssConstraintValidator.java b/src/org/scada_lts/web/security/XssConstraintValidator.java new file mode 100644 index 000000000..0915f217f --- /dev/null +++ b/src/org/scada_lts/web/security/XssConstraintValidator.java @@ -0,0 +1,21 @@ +package org.scada_lts.web.security; + +import org.scada_lts.serorepl.utils.StringUtils; +import org.scada_lts.web.beans.validation.AbstractConstraintValidator; +import org.scada_lts.web.beans.validation.ScadaValidator; + +public class XssConstraintValidator extends AbstractConstraintValidator { + + @Override + public void beforeValidate(String value) throws Exception { + if (StringUtils.isEmpty(value)) { + throw new XssValidatorException("Input is empty"); + } + } + + @Override + public void validate(String value) throws Exception { + ScadaValidator validator = new OwaspXssValidator(); + validator.validate(value); + } +} diff --git a/src/org/scada_lts/web/security/XssValid.java b/src/org/scada_lts/web/security/XssValid.java new file mode 100644 index 000000000..6923019fd --- /dev/null +++ b/src/org/scada_lts/web/security/XssValid.java @@ -0,0 +1,19 @@ +package org.scada_lts.web.security; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Constraint(validatedBy = XssConstraintValidator.class) +@Target({ ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface XssValid { + String message() default "Potential XSS detected in the request body."; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/src/org/scada_lts/web/security/XssValidatorException.java b/src/org/scada_lts/web/security/XssValidatorException.java new file mode 100644 index 000000000..7208ab42e --- /dev/null +++ b/src/org/scada_lts/web/security/XssValidatorException.java @@ -0,0 +1,25 @@ +package org.scada_lts.web.security; + +import org.scada_lts.web.beans.validation.ScadaValidatorException; + +public class XssValidatorException extends ScadaValidatorException { + + public XssValidatorException() { + } + + public XssValidatorException(String message) { + super(message); + } + + public XssValidatorException(String message, Throwable cause) { + super(message, cause); + } + + public XssValidatorException(Throwable cause) { + super(cause); + } + + public XssValidatorException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/test/org/scada_lts/web/security/XssUtilsTestsSuite.java b/test/org/scada_lts/web/security/XssUtilsTestsSuite.java new file mode 100644 index 000000000..ed6fb9572 --- /dev/null +++ b/test/org/scada_lts/web/security/XssUtilsTestsSuite.java @@ -0,0 +1,13 @@ +package org.scada_lts.web.security; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + XssUtilsValidateHttpQueryTest.class, + XssUtilsValidateHttpBodyTest.class, + XssUtilsValidateHttpBodyExceptionTest.class +}) +public class XssUtilsTestsSuite { +} \ No newline at end of file diff --git a/test/org/scada_lts/web/security/XssUtilsValidateHttpBodyExceptionTest.java b/test/org/scada_lts/web/security/XssUtilsValidateHttpBodyExceptionTest.java new file mode 100644 index 000000000..97d12fab3 --- /dev/null +++ b/test/org/scada_lts/web/security/XssUtilsValidateHttpBodyExceptionTest.java @@ -0,0 +1,40 @@ +package org.scada_lts.web.security; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import java.util.Arrays; +import java.util.Collection; + +@RunWith(Parameterized.class) +public class XssUtilsValidateHttpBodyExceptionTest { + + @Parameterized.Parameters + public static Collection testData() { + return Arrays.asList(new Object[][]{ + {null}, + {""}, + {"Link"}, + {"
Click me
"}, + {"body { background-image: url('\">'); }"}, + {"div { content: \"\"; }"}, + {"h1 { font-family: \"\"; }"}, + {"@import url(\"javascript:alert('XSS')\");"}, + {"div { /* comment: */ }"}, + {"span { content: '\">'; }"}, + {"h2 { color: expression(alert('XSS')); }"} + }); + } + + private final String input; + private final OwaspXssValidator owaspXssValidator = new OwaspXssValidator(); + + public XssUtilsValidateHttpBodyExceptionTest(String input) { + this.input = input; + } + + @Test(expected = XssValidatorException.class) + public void testValidateHttpBodyException() throws XssValidatorException { + owaspXssValidator.validate(input); + } +} diff --git a/test/org/scada_lts/web/security/XssUtilsValidateHttpBodyTest.java b/test/org/scada_lts/web/security/XssUtilsValidateHttpBodyTest.java new file mode 100644 index 000000000..22584408e --- /dev/null +++ b/test/org/scada_lts/web/security/XssUtilsValidateHttpBodyTest.java @@ -0,0 +1,43 @@ +package org.scada_lts.web.security; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collection; + +@RunWith(Parameterized.class) +public class XssUtilsValidateHttpBodyTest { + + @Parameterized.Parameters + public static Collection testData() { + return Arrays.asList(new Object[][]{ + {"Bold"}, + {"Hello, World!"}, + {""}, + {"Italic text"}, + {"Underlined"}, + {"Strong text"}, + {"Emphasized text"}, + {"

Paragraph with bold and italic

"}, + {"
  • Item 1
  • Item 2
"}, + {"
  1. First
  2. Second
"}, + {"body { font-size: 14px; }"}, + {"h1 { font-weight: bold; }"}, + {"p { margin: 0; padding: 0; }"}, + }); + } + + private final String input; + private final OwaspXssValidator owaspXssValidator = new OwaspXssValidator(); + + public XssUtilsValidateHttpBodyTest(String input) { + this.input = input; + } + + @Test + public void testValidateHttpBody() throws XssValidatorException { + owaspXssValidator.validate(input); + } +} diff --git a/test/org/scada_lts/web/security/XssUtilsTest.java b/test/org/scada_lts/web/security/XssUtilsValidateHttpQueryTest.java similarity index 97% rename from test/org/scada_lts/web/security/XssUtilsTest.java rename to test/org/scada_lts/web/security/XssUtilsValidateHttpQueryTest.java index 00902cfbe..80baa928b 100644 --- a/test/org/scada_lts/web/security/XssUtilsTest.java +++ b/test/org/scada_lts/web/security/XssUtilsValidateHttpQueryTest.java @@ -11,12 +11,12 @@ import java.util.Collection; @RunWith(Parameterized.class) -public class XssUtilsTest { +public class XssUtilsValidateHttpQueryTest { private final String input; private final boolean expectedResult; - public XssUtilsTest(String input, boolean expectedResult) { + public XssUtilsValidateHttpQueryTest(String input, boolean expectedResult) { this.input = input; this.expectedResult = expectedResult; }