From 5678b99ef5f987e6ccad7dcc807328087564c10d Mon Sep 17 00:00:00 2001 From: "Patrice.S" Date: Mon, 19 Sep 2022 16:54:20 +0200 Subject: [PATCH] spring-security-web: initial integration (#8530) Co-authored-by: CheeseHunter117 --- .../BindAuthenticatorFuzzer.java | 16 +++ projects/spring-security/Dockerfile | 2 +- .../EncodingUtilsConcatenateFuzzer.java | 16 +++ projects/spring-security/HexFuzzer.java | 16 +++ ...serDetailsManagerChangePasswordFuzzer.java | 16 +++ .../StrictHttpFirewallFuzzer.java | 135 ++++++++++++++++++ projects/spring-security/Utf8Fuzzer.java | 16 +++ projects/spring-security/build.sh | 54 +++---- projects/spring-security/diff.patch | 128 +++++++++++++++++ 9 files changed, 363 insertions(+), 36 deletions(-) create mode 100644 projects/spring-security/StrictHttpFirewallFuzzer.java create mode 100644 projects/spring-security/diff.patch diff --git a/projects/spring-security/BindAuthenticatorFuzzer.java b/projects/spring-security/BindAuthenticatorFuzzer.java index 6e40f0484..723eb1867 100644 --- a/projects/spring-security/BindAuthenticatorFuzzer.java +++ b/projects/spring-security/BindAuthenticatorFuzzer.java @@ -1,3 +1,19 @@ +// Copyright 2022 Google LLC +// +// Licensed 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. +// +//////////////////////////////////////////////////////////////////////////////// + import com.code_intelligence.jazzer.api.FuzzedDataProvider; import com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh; import org.springframework.security.ldap.DefaultSpringSecurityContextSource; diff --git a/projects/spring-security/Dockerfile b/projects/spring-security/Dockerfile index f2b529864..a82136866 100644 --- a/projects/spring-security/Dockerfile +++ b/projects/spring-security/Dockerfile @@ -19,8 +19,8 @@ FROM gcr.io/oss-fuzz-base/base-builder-jvm RUN apt update && apt install -y openjdk-17-jdk RUN git clone --depth 1 https://github.com/spring-projects/spring-security -RUN git clone --depth 1 https://github.com/spring-projects/spring-ldap spring-ldap COPY build.sh $SRC/ COPY *Fuzzer.java $SRC/ +COPY *.patch $SRC/ WORKDIR $SRC/spring-security \ No newline at end of file diff --git a/projects/spring-security/EncodingUtilsConcatenateFuzzer.java b/projects/spring-security/EncodingUtilsConcatenateFuzzer.java index 26ba3278a..bc354235f 100644 --- a/projects/spring-security/EncodingUtilsConcatenateFuzzer.java +++ b/projects/spring-security/EncodingUtilsConcatenateFuzzer.java @@ -1,3 +1,19 @@ +// Copyright 2022 Google LLC +// +// Licensed 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. +// +//////////////////////////////////////////////////////////////////////////////// + import com.code_intelligence.jazzer.api.FuzzedDataProvider; import org.springframework.security.crypto.util.EncodingUtils; diff --git a/projects/spring-security/HexFuzzer.java b/projects/spring-security/HexFuzzer.java index f183852e3..477f4fe33 100644 --- a/projects/spring-security/HexFuzzer.java +++ b/projects/spring-security/HexFuzzer.java @@ -1,3 +1,19 @@ +// Copyright 2022 Google LLC +// +// Licensed 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. +// +//////////////////////////////////////////////////////////////////////////////// + import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow; import java.lang.CharSequence; diff --git a/projects/spring-security/InMemoryUserDetailsManagerChangePasswordFuzzer.java b/projects/spring-security/InMemoryUserDetailsManagerChangePasswordFuzzer.java index 0de6c31bb..65707ebf1 100644 --- a/projects/spring-security/InMemoryUserDetailsManagerChangePasswordFuzzer.java +++ b/projects/spring-security/InMemoryUserDetailsManagerChangePasswordFuzzer.java @@ -1,3 +1,19 @@ +// Copyright 2022 Google LLC +// +// Licensed 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. +// +//////////////////////////////////////////////////////////////////////////////// + import com.code_intelligence.jazzer.api.FuzzedDataProvider; import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow; import com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh; diff --git a/projects/spring-security/StrictHttpFirewallFuzzer.java b/projects/spring-security/StrictHttpFirewallFuzzer.java new file mode 100644 index 000000000..223d151aa --- /dev/null +++ b/projects/spring-security/StrictHttpFirewallFuzzer.java @@ -0,0 +1,135 @@ +// Copyright 2022 Google LLC +// +// Licensed 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. +// +//////////////////////////////////////////////////////////////////////////////// + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.http.HttpMethod; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.security.web.firewall.StrictHttpFirewall; +import org.springframework.security.web.firewall.RequestRejectedException; +import com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium; +import java.util.function.Predicate; +import java.util.Enumeration; +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import java.util.ArrayList; +import java.util.List; +import jakarta.servlet.http.HttpServletRequest; +import java.math.BigInteger; + +public class StrictHttpFirewallFuzzer { + record Header(String n, String v) {}; + + // https://github.com/spring-projects/spring-security/blob/main/web/src/main/java/org/springframework/security/web/firewall/StrictHttpFirewall.java#L127 + private static Pattern ASSIGNED_AND_NOT_ISO_CONTROL_PATTERN = Pattern.compile("[\\p{IsAssigned}||[\\p{IsControl}]]*"); + private static Predicate ASSIGNED_AND_NOT_ISO_CONTROL_PREDICATE = (s) -> ASSIGNED_AND_NOT_ISO_CONTROL_PATTERN.matcher(s).matches(); + + public static void fuzzerTestOneInput(FuzzedDataProvider data) throws Exception { + StrictHttpFirewall firewall = new StrictHttpFirewall(); + MockHttpServletRequest request = new MockHttpServletRequest("GET", ""); + + boolean invalidMethod = data.consumeBoolean(); + + if (invalidMethod) { + request.setMethod(data.consumeString(50)); + } + + List
maliciousHeaders = new ArrayList
(); + List maliciousParameterNames = new ArrayList(); + List maliciousUrls = new ArrayList(); + + // Feed fuzzer data into the request + for (int i = 0; i < data.consumeInt(1, 5); i++) { + String url = data.consumeString(400); + switch (data.consumeInt(0, 5)) { + case 0: + request.setPathInfo(url); + maliciousUrls.add(url); + break; + case 1: + request.setContextPath(url); + maliciousUrls.add(url); + break; + case 2: + request.setRequestURI(url); + maliciousUrls.add(url); + break; + case 3: + request.setServletPath(url); + maliciousUrls.add(url); + break; + case 4: + String parameterName = data.consumeAsciiString(100).trim(); + if (parameterName.isEmpty()) { + break; + } + request.addParameter(parameterName, "Dummy value"); + maliciousParameterNames.add(parameterName); + break; + case 5: + Header header = new Header(data.consumeString(100), data.consumeString(100)); + if (header.v().isEmpty() || header.n().isEmpty()) { + break; + } + request.addHeader(header.n(), header.v()); + maliciousHeaders.add(header); + } + } + + HttpServletRequest servletRequest; + try { + servletRequest = firewall.getFirewalledRequest(request); + + // getHeader() and getParameter() should throw a rejection exception if it contains invalid chars + for (Header header : maliciousHeaders) { + servletRequest.getHeader(header.n()); + } + + for (String parameterName : maliciousParameterNames) { + servletRequest.getParameter(parameterName); + } + + } catch (RequestRejectedException e) { + return; + } + + for (String forbiddenChar : firewall.getEncodedUrlBlocklist()) { + if (request.getPathInfo() != null && request.getPathInfo().contains(forbiddenChar) + || request.getRequestURI() != null && request.getRequestURI().contains(forbiddenChar) + || request.getContextPath() != null && request.getContextPath().contains(forbiddenChar) + || request.getServletPath() != null && request.getServletPath().contains(forbiddenChar)) { + throw new FuzzerSecurityIssueMedium("Malicious char not filtered: " + forbiddenChar); + } + } + + for (Header header : maliciousHeaders) { + validate(header.n()); + String v = servletRequest.getHeader(header.n()); + validate(v); + } + + for (String name : maliciousParameterNames) { + validate(name); + } + } + + // Wrapper for validateAllowedHeaderName, validateAllowedHeaderValue, validateAllowedParameterName & validateAllowedParameterValue + private static void validate(String value) { + if (!value.isEmpty() && ASSIGNED_AND_NOT_ISO_CONTROL_PREDICATE.test(value)) { + throw new FuzzerSecurityIssueMedium("Malicious char not filtered: " + value); + } + } +} diff --git a/projects/spring-security/Utf8Fuzzer.java b/projects/spring-security/Utf8Fuzzer.java index 4ada42205..9923a17be 100644 --- a/projects/spring-security/Utf8Fuzzer.java +++ b/projects/spring-security/Utf8Fuzzer.java @@ -1,3 +1,19 @@ +// Copyright 2022 Google LLC +// +// Licensed 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. +// +//////////////////////////////////////////////////////////////////////////////// + import com.code_intelligence.jazzer.api.FuzzedDataProvider; import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow; diff --git a/projects/spring-security/build.sh b/projects/spring-security/build.sh index 7547b9c6c..bb272595a 100755 --- a/projects/spring-security/build.sh +++ b/projects/spring-security/build.sh @@ -19,46 +19,28 @@ export JAVA_HOME="$OUT/open-jdk-17" mkdir -p $JAVA_HOME rsync -aL --exclude=*.zip "/usr/lib/jvm/java-17-openjdk-amd64/" "$JAVA_HOME" -cat > patch.diff <<- EOM -diff --git a/ldap/spring-security-ldap.gradle b/ldap/spring-security-ldap.gradle -index c4f6c08..413b992 100644 ---- a/ldap/spring-security-ldap.gradle -+++ b/ldap/spring-security-ldap.gradle -@@ -1,4 +1,9 @@ -+plugins { -+ id 'com.github.johnrengelman.shadow' version '7.1.2' -+} -+ - apply plugin: 'io.spring.convention.spring-module' -+apply plugin: 'com.github.johnrengelman.shadow' +git apply $SRC/*.patch - dependencies { - management platform(project(":spring-security-dependencies")) -@@ -10,7 +15,7 @@ dependencies { +CURRENT_VERSION=$(sed -nr "s/^version=(.*)/\1/p" gradle.properties) - optional 'com.fasterxml.jackson.core:jackson-databind' - optional 'ldapsdk:ldapsdk' -- optional "com.unboundid:unboundid-ldapsdk" -+ api "com.unboundid:unboundid-ldapsdk" - optional "org.apache.directory.server:apacheds-core" - optional "org.apache.directory.server:apacheds-core-entry" - optional "org.apache.directory.server:apacheds-protocol-shared" -EOM +GRADLE_ARGS="-x test -x javadoc" -git apply patch.diff +./gradlew shadowJar $GRADLE_ARGS -b ldap/spring-security-ldap.gradle +./gradlew shadowJar $GRADLE_ARGS -b config/spring-security-config.gradle +./gradlew shadowJar $GRADLE_ARGS -b core/spring-security-core.gradle +./gradlew build -b dependencies/spring-security-dependencies.gradle +./gradlew shadowJar $GRADLE_ARGS -b messaging/spring-security-messaging.gradle +./gradlew shadowJar $GRADLE_ARGS -b web/spring-security-web.gradle +./gradlew shadowJar $GRADLE_ARGS -b test/spring-security-test.gradle +# Copy all shadow jars to the $OUT folder +find . -name "*-all.jar" -print0 | while read -d $'\0' file +do + file_name=`echo $file | sed "s/-$CURRENT_VERSION-all//g" | egrep "[^\/]*.jar" -o` + cp $file $OUT/$file_name +done -CURRENT_VERSION=$(./gradlew properties --no-daemon --console=plain | sed -nr "s/^version:\ (.*)/\1/p") - -./gradlew build -PbuildSrc.skipTests -x test -i -x javadoc -x :spring-security-docs:api -x :spring-security-itest-ldap-embedded-none:integrationTest -x :spring-security-config:integrationTest -./gradlew shadowJar --build-file ldap/spring-security-ldap.gradle -PbuildSrc.skipTests -x test -x javadoc -x :spring-security-itest-ldap-embedded-none:integrationTest -cp "core/build/libs/spring-security-core-$CURRENT_VERSION.jar" "$OUT/spring-security-core.jar" -cp "ldap/build/libs/spring-security-ldap-$CURRENT_VERSION-all.jar" "$OUT/spring-security-ldap.jar" -cp "build/libs/spring-security-$CURRENT_VERSION.jar" "$OUT/spring-security.jar" -cp "config/build/libs/spring-security-config-$CURRENT_VERSION.jar" "$OUT/spring-security-config.jar" -cp "config/build/libs/spring-security-config-$CURRENT_VERSION-test.jar" "$OUT/spring-security-config-test.jar" - -ALL_JARS="spring-security-ldap.jar spring-security-core.jar spring-security.jar spring-security-config.jar spring-security-config-test.jar" +ALL_JARS=`ls $OUT/*.jar -I jazzer_agent_deploy.jar -1 | tr "\n" " " | egrep "[^\/]*.jar" -o` # The class path at build-time includes the project jars in $OUT as well as the # Jazzer API. @@ -86,3 +68,5 @@ LD_LIBRARY_PATH=\"\$this_dir/open-jdk-17/lib/server\":\$this_dir \ \$@" > $OUT/$fuzzer_basename chmod u+x $OUT/$fuzzer_basename done + +cp $SRC/StrictHttpFirewallFuzzer\$Header.class $OUT/ \ No newline at end of file diff --git a/projects/spring-security/diff.patch b/projects/spring-security/diff.patch new file mode 100644 index 000000000..7ecbd00ad --- /dev/null +++ b/projects/spring-security/diff.patch @@ -0,0 +1,128 @@ +diff --git a/build.gradle b/build.gradle +diff --git a/build.gradle b/build.gradle +index 21893a7..faf7dff 100644 +--- a/build.gradle ++++ b/build.gradle +@@ -2,6 +2,7 @@ import io.spring.gradle.IncludeRepoTask + + buildscript { + dependencies { ++ classpath "gradle.plugin.com.github.johnrengelman:shadow:7.1.2" + classpath "io.spring.javaformat:spring-javaformat-gradle-plugin:$springJavaformatVersion" + classpath 'io.spring.nohttp:nohttp-gradle:0.0.10' + classpath "io.freefair.gradle:aspectj-plugin:6.5.1" +@@ -14,6 +15,7 @@ buildscript { + } + } + ++apply plugin: 'com.github.johnrengelman.shadow' + apply plugin: 'io.spring.nohttp' + apply plugin: 'locks' + apply plugin: 's101' +diff --git a/config/spring-security-config.gradle b/config/spring-security-config.gradle +index 7818f34..4ef9150 100644 +--- a/config/spring-security-config.gradle ++++ b/config/spring-security-config.gradle +@@ -1,5 +1,6 @@ + import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + ++apply plugin: 'com.github.johnrengelman.shadow' + apply plugin: 'io.spring.convention.spring-module' + apply plugin: 'trang' + apply plugin: 'kotlin' +diff --git a/core/spring-security-core.gradle b/core/spring-security-core.gradle +index 2968d3d..9a1be67 100644 +--- a/core/spring-security-core.gradle ++++ b/core/spring-security-core.gradle +@@ -1,5 +1,6 @@ + import java.util.concurrent.Callable + ++apply plugin: 'com.github.johnrengelman.shadow' + apply plugin: 'io.spring.convention.spring-module' + + dependencies { +diff --git a/data/spring-security-data.gradle b/data/spring-security-data.gradle +index 3e915ef..1b4d55b 100644 +--- a/data/spring-security-data.gradle ++++ b/data/spring-security-data.gradle +@@ -1,3 +1,4 @@ ++apply plugin: 'com.github.johnrengelman.shadow' + apply plugin: 'io.spring.convention.spring-module' + + dependencies { +diff --git a/dependencies/spring-security-dependencies.gradle b/dependencies/spring-security-dependencies.gradle +index fb306f6..b27cd44 100644 +--- a/dependencies/spring-security-dependencies.gradle ++++ b/dependencies/spring-security-dependencies.gradle +@@ -6,6 +6,8 @@ javaPlatform { + allowDependencies() + } + ++apply plugin: "com.github.johnrengelman.shadow" ++ + dependencies { + api platform("org.springframework:spring-framework-bom:$springFrameworkVersion") + api platform("io.projectreactor:reactor-bom:2022.0.0-M4") +diff --git a/ldap/spring-security-ldap.gradle b/ldap/spring-security-ldap.gradle +index c4f6c08..39023ed 100644 +--- a/ldap/spring-security-ldap.gradle ++++ b/ldap/spring-security-ldap.gradle +@@ -1,3 +1,4 @@ ++apply plugin: 'com.github.johnrengelman.shadow' + apply plugin: 'io.spring.convention.spring-module' + + dependencies { +@@ -10,7 +11,7 @@ dependencies { + + optional 'com.fasterxml.jackson.core:jackson-databind' + optional 'ldapsdk:ldapsdk' +- optional "com.unboundid:unboundid-ldapsdk" ++ api "com.unboundid:unboundid-ldapsdk" + optional "org.apache.directory.server:apacheds-core" + optional "org.apache.directory.server:apacheds-core-entry" + optional "org.apache.directory.server:apacheds-protocol-shared" +diff --git a/messaging/spring-security-messaging.gradle b/messaging/spring-security-messaging.gradle +index 64435e6..9f8526f 100644 +--- a/messaging/spring-security-messaging.gradle ++++ b/messaging/spring-security-messaging.gradle +@@ -1,3 +1,4 @@ ++apply plugin: 'com.github.johnrengelman.shadow' + apply plugin: 'io.spring.convention.spring-module' + + dependencies { +diff --git a/oauth2/oauth2-core/spring-security-oauth2-core.gradle b/oauth2/oauth2-core/spring-security-oauth2-core.gradle +index 9fb4449..101532c 100644 +--- a/oauth2/oauth2-core/spring-security-oauth2-core.gradle ++++ b/oauth2/oauth2-core/spring-security-oauth2-core.gradle +@@ -1,3 +1,4 @@ ++apply plugin: 'com.github.johnrengelman.shadow' + apply plugin: 'io.spring.convention.spring-module' + + dependencies { +diff --git a/test/spring-security-test.gradle b/test/spring-security-test.gradle +index 92b3868..b24d835 100644 +--- a/test/spring-security-test.gradle ++++ b/test/spring-security-test.gradle +@@ -1,3 +1,4 @@ ++apply plugin: "com.github.johnrengelman.shadow" + apply plugin: 'io.spring.convention.spring-module' + + dependencies { +diff --git a/web/spring-security-web.gradle b/web/spring-security-web.gradle +index ca63924..45aea6f 100644 +--- a/web/spring-security-web.gradle ++++ b/web/spring-security-web.gradle +@@ -1,3 +1,4 @@ ++apply plugin: 'com.github.johnrengelman.shadow' + apply plugin: 'io.spring.convention.spring-module' + + dependencies { +@@ -17,7 +18,7 @@ dependencies { + optional 'org.springframework:spring-webflux' + optional 'org.springframework:spring-webmvc' + +- provided 'jakarta.servlet:jakarta.servlet-api' ++ api 'jakarta.servlet:jakarta.servlet-api' + + testImplementation project(path: ':spring-security-core', configuration: 'tests') + testImplementation 'io.projectreactor:reactor-test'