diff --git a/MODULE.bazel b/MODULE.bazel index 9313f6cce..d3eabf2ba 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -136,6 +136,9 @@ VULNERABLE_TEST_MAVEN_ARTIFACTS = [ "org.apache.xmlgraphics:batik-xml:1.14", "org.glassfish:javax.el:3.0.1-b06", "org.hibernate:hibernate-validator:5.2.4.Final", + "org.springframework.cloud:spring-cloud-function-context:3.1.6", + "org.springframework.cloud:spring-cloud-function-core:3.1.6", + "org.springframework:spring-messaging:6.1.4", ] maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel index 04eac4178..1ffd0e353 100644 --- a/examples/BUILD.bazel +++ b/examples/BUILD.bazel @@ -334,6 +334,26 @@ java_fuzz_target_test( ], ) +java_fuzz_target_test( + name = "SpringCloudFunctionRoutingFuzzer", + srcs = [ + "src/main/java/com/example/SpringCloudFunctionRoutingFuzzer.java", + ], + allowed_findings = [ + "com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh", + ], + fuzzer_args = ["-runs=100000"], + tags = ["no-jdk8"], + target_class = "com.example.SpringCloudFunctionRoutingFuzzer", + verify_crash_reproducer = False, + deps = [ + "@maven//:com_fasterxml_jackson_core_jackson_databind", + "@maven//:org_springframework_cloud_spring_cloud_function_context", + "@maven//:org_springframework_cloud_spring_cloud_function_core", + "@maven//:org_springframework_spring_messaging", + ], +) + java_fuzz_target_test( name = "JacksonCborFuzzer", srcs = [ diff --git a/examples/src/main/java/com/example/SpringCloudFunctionRoutingFuzzer.java b/examples/src/main/java/com/example/SpringCloudFunctionRoutingFuzzer.java new file mode 100644 index 000000000..6eb058df0 --- /dev/null +++ b/examples/src/main/java/com/example/SpringCloudFunctionRoutingFuzzer.java @@ -0,0 +1,61 @@ +/* + * Copyright 2025 Code Intelligence GmbH + * + * 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. + */ + +package com.example; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.LogManager; +import org.springframework.cloud.function.context.FunctionProperties; +import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry; +import org.springframework.cloud.function.context.config.RoutingFunction; +import org.springframework.cloud.function.json.JacksonMapper; +import org.springframework.messaging.Message; +import org.springframework.messaging.converter.CompositeMessageConverter; +import org.springframework.messaging.converter.StringMessageConverter; +import org.springframework.messaging.support.MessageBuilder; + +/** + * Reproduce CVE-2022-22963 by fuzzing the + * routing-expression header in Spring Cloud Function. + */ +public class SpringCloudFunctionRoutingFuzzer { + private static RoutingFunction router; + + public static void fuzzerInitialize() { + LogManager.getLogManager().getLogger("").setLevel(Level.SEVERE); + // Empty function registry + SimpleFunctionRegistry registry = + new SimpleFunctionRegistry( + null, + new CompositeMessageConverter(Arrays.asList(new StringMessageConverter())), + new JacksonMapper(new ObjectMapper())); + router = new RoutingFunction(registry, new FunctionProperties()); + } + + public static void fuzzerTestOneInput(String payload, String expr) { + try { + Message message = + MessageBuilder.withPayload(payload) + .setHeader("spring.cloud.function.routing-expression", expr) + .build(); + router.apply(message); + } catch (Throwable ignored) { + // Most inputs will cause parsing or routing errors; that is fine. + } + } +} diff --git a/maven_install.json b/maven_install.json index dcd73dc46..b45173996 100755 --- a/maven_install.json +++ b/maven_install.json @@ -1,7 +1,7 @@ { "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL", - "__INPUT_ARTIFACTS_HASH": 22269842, - "__RESOLVED_ARTIFACTS_HASH": 236949396, + "__INPUT_ARTIFACTS_HASH": 1534174847, + "__RESOLVED_ARTIFACTS_HASH": 1096484833, "conflict_resolution": { "com.google.code.gson:gson:2.8.6": "com.google.code.gson:gson:2.8.9", "com.google.j2objc:j2objc-annotations:2.8": "com.google.j2objc:j2objc-annotations:3.1", @@ -188,6 +188,12 @@ }, "version": "1.12.3" }, + "io.projectreactor:reactor-core": { + "shasums": { + "jar": "12ff3eae092bf07f6651a93b9292a87fbee7f5b9ad4c9cc01189b1a4f0979973" + }, + "version": "3.4.12" + }, "jakarta.el:jakarta.el-api": { "shasums": { "jar": "7e84b5bed49de32b79cc5e85d90b6f5adb1a953ac67283adbb41c1e297f9c605" @@ -206,6 +212,12 @@ }, "version": "1.2.0" }, + "javax.annotation:javax.annotation-api": { + "shasums": { + "jar": "e04ba5195bcd555dc95650f7cc614d151e4bcd52d29a10b8aa2197f3ab89ab9b" + }, + "version": "1.3.2" + }, "javax.el:javax.el-api": { "shasums": { "jar": "0b46b36709ecbb9791ac4ba44d16125b9d65b576112afdaaa286052b6e498bc4" @@ -572,6 +584,36 @@ }, "version": "9.9" }, + "org.reactivestreams:reactive-streams": { + "shasums": { + "jar": "1dee0481072d19c929b623e155e14d2f6085dc011529a0a0dbefc84cf571d865" + }, + "version": "1.0.3" + }, + "org.springframework.boot:spring-boot": { + "shasums": { + "jar": "fca95eb7a9547b89f9722010eceae9604a098dfbfb4cf37f80ed16354bcfdaba" + }, + "version": "2.4.13" + }, + "org.springframework.boot:spring-boot-autoconfigure": { + "shasums": { + "jar": "f7776b60bbaeda3cec1d44c3fbd875530d124bc0b4e330a22aef0148fc06794e" + }, + "version": "2.4.13" + }, + "org.springframework.cloud:spring-cloud-function-context": { + "shasums": { + "jar": "c0ef37245f521774f9e540bc258981c5931e4c462b246fd452c52f861ebad47b" + }, + "version": "3.1.6" + }, + "org.springframework.cloud:spring-cloud-function-core": { + "shasums": { + "jar": "2e11536d4667402d30befdbb050bba992a12eb9d95303d546038a37d848a92a6" + }, + "version": "3.1.6" + }, "org.springframework:spring-aop": { "shasums": { "jar": "de43e11b649cbc0294dc6d7a5fd82c2b07a55114a5b6558d776c115ad5719a16" @@ -608,6 +650,12 @@ }, "version": "6.1.4" }, + "org.springframework:spring-messaging": { + "shasums": { + "jar": "3f84fa9a4963a1a1764a235ee76bb85af6012f1c2358a399ddaad065889d271e" + }, + "version": "6.1.4" + }, "org.springframework:spring-test": { "shasums": { "jar": "43ea85da5dc0a4b4c250aa2e12b77bb679bd63c7cd765c80c73c7b52bed47402" @@ -709,6 +757,9 @@ "io.micrometer:micrometer-observation": [ "io.micrometer:micrometer-commons" ], + "io.projectreactor:reactor-core": [ + "org.reactivestreams:reactive-streams" + ], "javax.xml.bind:jaxb-api": [ "javax.activation:javax.activation-api" ], @@ -909,6 +960,26 @@ "org.ow2.asm:asm-tree": [ "org.ow2.asm:asm" ], + "org.springframework.boot:spring-boot": [ + "org.springframework:spring-context", + "org.springframework:spring-core" + ], + "org.springframework.boot:spring-boot-autoconfigure": [ + "org.springframework.boot:spring-boot" + ], + "org.springframework.cloud:spring-cloud-function-context": [ + "com.fasterxml.jackson.core:jackson-databind", + "javax.annotation:javax.annotation-api", + "net.jodah:typetools", + "org.springframework.boot:spring-boot-autoconfigure", + "org.springframework.cloud:spring-cloud-function-core", + "org.springframework:spring-messaging" + ], + "org.springframework.cloud:spring-cloud-function-core": [ + "io.projectreactor:reactor-core", + "javax.annotation:javax.annotation-api", + "org.springframework:spring-core" + ], "org.springframework:spring-aop": [ "org.springframework:spring-beans", "org.springframework:spring-core" @@ -929,6 +1000,10 @@ "org.springframework:spring-expression": [ "org.springframework:spring-core" ], + "org.springframework:spring-messaging": [ + "org.springframework:spring-beans", + "org.springframework:spring-core" + ], "org.springframework:spring-test": [ "org.springframework:spring-core" ], @@ -1277,6 +1352,18 @@ "io.micrometer.observation.docs", "io.micrometer.observation.transport" ], + "io.projectreactor:reactor-core": [ + "reactor.adapter", + "reactor.core", + "reactor.core.publisher", + "reactor.core.scheduler", + "reactor.util", + "reactor.util.annotation", + "reactor.util.concurrent", + "reactor.util.context", + "reactor.util.function", + "reactor.util.retry" + ], "jakarta.el:jakarta.el-api": [ "jakarta.el" ], @@ -1289,6 +1376,11 @@ "javax.activation:javax.activation-api": [ "javax.activation" ], + "javax.annotation:javax.annotation-api": [ + "javax.annotation", + "javax.annotation.security", + "javax.annotation.sql" + ], "javax.el:javax.el-api": [ "javax.el" ], @@ -2160,6 +2252,200 @@ "org.ow2.asm:asm-tree": [ "org.objectweb.asm.tree" ], + "org.reactivestreams:reactive-streams": [ + "org.reactivestreams" + ], + "org.springframework.boot:spring-boot": [ + "org.springframework.boot", + "org.springframework.boot.admin", + "org.springframework.boot.ansi", + "org.springframework.boot.availability", + "org.springframework.boot.builder", + "org.springframework.boot.cloud", + "org.springframework.boot.context", + "org.springframework.boot.context.annotation", + "org.springframework.boot.context.config", + "org.springframework.boot.context.event", + "org.springframework.boot.context.logging", + "org.springframework.boot.context.metrics.buffering", + "org.springframework.boot.context.properties", + "org.springframework.boot.context.properties.bind", + "org.springframework.boot.context.properties.bind.handler", + "org.springframework.boot.context.properties.bind.validation", + "org.springframework.boot.context.properties.source", + "org.springframework.boot.convert", + "org.springframework.boot.diagnostics", + "org.springframework.boot.diagnostics.analyzer", + "org.springframework.boot.env", + "org.springframework.boot.info", + "org.springframework.boot.jackson", + "org.springframework.boot.jdbc", + "org.springframework.boot.jdbc.metadata", + "org.springframework.boot.jms", + "org.springframework.boot.json", + "org.springframework.boot.jta.atomikos", + "org.springframework.boot.jta.bitronix", + "org.springframework.boot.liquibase", + "org.springframework.boot.logging", + "org.springframework.boot.logging.java", + "org.springframework.boot.logging.log4j2", + "org.springframework.boot.logging.logback", + "org.springframework.boot.origin", + "org.springframework.boot.orm.jpa", + "org.springframework.boot.orm.jpa.hibernate", + "org.springframework.boot.reactor", + "org.springframework.boot.rsocket.context", + "org.springframework.boot.rsocket.messaging", + "org.springframework.boot.rsocket.netty", + "org.springframework.boot.rsocket.server", + "org.springframework.boot.security.reactive", + "org.springframework.boot.security.servlet", + "org.springframework.boot.system", + "org.springframework.boot.task", + "org.springframework.boot.type.classreading", + "org.springframework.boot.util", + "org.springframework.boot.validation", + "org.springframework.boot.validation.beanvalidation", + "org.springframework.boot.web.client", + "org.springframework.boot.web.codec", + "org.springframework.boot.web.context", + "org.springframework.boot.web.embedded.jetty", + "org.springframework.boot.web.embedded.netty", + "org.springframework.boot.web.embedded.tomcat", + "org.springframework.boot.web.embedded.undertow", + "org.springframework.boot.web.error", + "org.springframework.boot.web.reactive.context", + "org.springframework.boot.web.reactive.error", + "org.springframework.boot.web.reactive.filter", + "org.springframework.boot.web.reactive.function.client", + "org.springframework.boot.web.reactive.result.view", + "org.springframework.boot.web.reactive.server", + "org.springframework.boot.web.server", + "org.springframework.boot.web.servlet", + "org.springframework.boot.web.servlet.context", + "org.springframework.boot.web.servlet.error", + "org.springframework.boot.web.servlet.filter", + "org.springframework.boot.web.servlet.server", + "org.springframework.boot.web.servlet.support", + "org.springframework.boot.web.servlet.view", + "org.springframework.boot.webservices.client" + ], + "org.springframework.boot:spring-boot-autoconfigure": [ + "org.springframework.boot.autoconfigure", + "org.springframework.boot.autoconfigure.admin", + "org.springframework.boot.autoconfigure.amqp", + "org.springframework.boot.autoconfigure.aop", + "org.springframework.boot.autoconfigure.availability", + "org.springframework.boot.autoconfigure.batch", + "org.springframework.boot.autoconfigure.cache", + "org.springframework.boot.autoconfigure.cassandra", + "org.springframework.boot.autoconfigure.codec", + "org.springframework.boot.autoconfigure.condition", + "org.springframework.boot.autoconfigure.context", + "org.springframework.boot.autoconfigure.couchbase", + "org.springframework.boot.autoconfigure.dao", + "org.springframework.boot.autoconfigure.data", + "org.springframework.boot.autoconfigure.data.cassandra", + "org.springframework.boot.autoconfigure.data.couchbase", + "org.springframework.boot.autoconfigure.data.elasticsearch", + "org.springframework.boot.autoconfigure.data.jdbc", + "org.springframework.boot.autoconfigure.data.jpa", + "org.springframework.boot.autoconfigure.data.ldap", + "org.springframework.boot.autoconfigure.data.mongo", + "org.springframework.boot.autoconfigure.data.neo4j", + "org.springframework.boot.autoconfigure.data.r2dbc", + "org.springframework.boot.autoconfigure.data.redis", + "org.springframework.boot.autoconfigure.data.rest", + "org.springframework.boot.autoconfigure.data.solr", + "org.springframework.boot.autoconfigure.data.web", + "org.springframework.boot.autoconfigure.diagnostics.analyzer", + "org.springframework.boot.autoconfigure.domain", + "org.springframework.boot.autoconfigure.elasticsearch", + "org.springframework.boot.autoconfigure.elasticsearch.rest", + "org.springframework.boot.autoconfigure.flyway", + "org.springframework.boot.autoconfigure.freemarker", + "org.springframework.boot.autoconfigure.groovy.template", + "org.springframework.boot.autoconfigure.gson", + "org.springframework.boot.autoconfigure.h2", + "org.springframework.boot.autoconfigure.hateoas", + "org.springframework.boot.autoconfigure.hazelcast", + "org.springframework.boot.autoconfigure.http", + "org.springframework.boot.autoconfigure.http.codec", + "org.springframework.boot.autoconfigure.influx", + "org.springframework.boot.autoconfigure.info", + "org.springframework.boot.autoconfigure.integration", + "org.springframework.boot.autoconfigure.jackson", + "org.springframework.boot.autoconfigure.jdbc", + "org.springframework.boot.autoconfigure.jdbc.metadata", + "org.springframework.boot.autoconfigure.jersey", + "org.springframework.boot.autoconfigure.jms", + "org.springframework.boot.autoconfigure.jms.activemq", + "org.springframework.boot.autoconfigure.jms.artemis", + "org.springframework.boot.autoconfigure.jmx", + "org.springframework.boot.autoconfigure.jooq", + "org.springframework.boot.autoconfigure.jsonb", + "org.springframework.boot.autoconfigure.kafka", + "org.springframework.boot.autoconfigure.ldap", + "org.springframework.boot.autoconfigure.ldap.embedded", + "org.springframework.boot.autoconfigure.liquibase", + "org.springframework.boot.autoconfigure.logging", + "org.springframework.boot.autoconfigure.mail", + "org.springframework.boot.autoconfigure.mongo", + "org.springframework.boot.autoconfigure.mongo.embedded", + "org.springframework.boot.autoconfigure.mustache", + "org.springframework.boot.autoconfigure.neo4j", + "org.springframework.boot.autoconfigure.orm.jpa", + "org.springframework.boot.autoconfigure.quartz", + "org.springframework.boot.autoconfigure.r2dbc", + "org.springframework.boot.autoconfigure.rsocket", + "org.springframework.boot.autoconfigure.security", + "org.springframework.boot.autoconfigure.security.oauth2.client", + "org.springframework.boot.autoconfigure.security.oauth2.client.reactive", + "org.springframework.boot.autoconfigure.security.oauth2.client.servlet", + "org.springframework.boot.autoconfigure.security.oauth2.resource", + "org.springframework.boot.autoconfigure.security.oauth2.resource.reactive", + "org.springframework.boot.autoconfigure.security.oauth2.resource.servlet", + "org.springframework.boot.autoconfigure.security.reactive", + "org.springframework.boot.autoconfigure.security.rsocket", + "org.springframework.boot.autoconfigure.security.saml2", + "org.springframework.boot.autoconfigure.security.servlet", + "org.springframework.boot.autoconfigure.sendgrid", + "org.springframework.boot.autoconfigure.session", + "org.springframework.boot.autoconfigure.solr", + "org.springframework.boot.autoconfigure.task", + "org.springframework.boot.autoconfigure.template", + "org.springframework.boot.autoconfigure.thymeleaf", + "org.springframework.boot.autoconfigure.transaction", + "org.springframework.boot.autoconfigure.transaction.jta", + "org.springframework.boot.autoconfigure.validation", + "org.springframework.boot.autoconfigure.web", + "org.springframework.boot.autoconfigure.web.client", + "org.springframework.boot.autoconfigure.web.embedded", + "org.springframework.boot.autoconfigure.web.format", + "org.springframework.boot.autoconfigure.web.reactive", + "org.springframework.boot.autoconfigure.web.reactive.error", + "org.springframework.boot.autoconfigure.web.reactive.function.client", + "org.springframework.boot.autoconfigure.web.servlet", + "org.springframework.boot.autoconfigure.web.servlet.error", + "org.springframework.boot.autoconfigure.webservices", + "org.springframework.boot.autoconfigure.webservices.client", + "org.springframework.boot.autoconfigure.websocket.reactive", + "org.springframework.boot.autoconfigure.websocket.servlet" + ], + "org.springframework.cloud:spring-cloud-function-context": [ + "org.springframework.cloud.function.actuator", + "org.springframework.cloud.function.cloudevent", + "org.springframework.cloud.function.context", + "org.springframework.cloud.function.context.catalog", + "org.springframework.cloud.function.context.config", + "org.springframework.cloud.function.context.message", + "org.springframework.cloud.function.context.test", + "org.springframework.cloud.function.json", + "org.springframework.cloud.function.utils" + ], + "org.springframework.cloud:spring-cloud-function-core": [ + "org.springframework.cloud.function.core" + ], "org.springframework:spring-aop": [ "org.aopalliance.aop", "org.aopalliance.intercept", @@ -2327,6 +2613,31 @@ "org.apache.commons.logging", "org.apache.commons.logging.impl" ], + "org.springframework:spring-messaging": [ + "org.springframework.messaging", + "org.springframework.messaging.converter", + "org.springframework.messaging.core", + "org.springframework.messaging.handler", + "org.springframework.messaging.handler.annotation", + "org.springframework.messaging.handler.annotation.reactive", + "org.springframework.messaging.handler.annotation.support", + "org.springframework.messaging.handler.invocation", + "org.springframework.messaging.handler.invocation.reactive", + "org.springframework.messaging.rsocket", + "org.springframework.messaging.rsocket.annotation", + "org.springframework.messaging.rsocket.annotation.support", + "org.springframework.messaging.rsocket.service", + "org.springframework.messaging.simp", + "org.springframework.messaging.simp.annotation", + "org.springframework.messaging.simp.annotation.support", + "org.springframework.messaging.simp.broker", + "org.springframework.messaging.simp.config", + "org.springframework.messaging.simp.stomp", + "org.springframework.messaging.simp.user", + "org.springframework.messaging.support", + "org.springframework.messaging.tcp", + "org.springframework.messaging.tcp.reactor" + ], "org.springframework:spring-test": [ "org.springframework.mock.env", "org.springframework.mock.http", @@ -2586,9 +2897,11 @@ "io.github.classgraph:classgraph", "io.micrometer:micrometer-commons", "io.micrometer:micrometer-observation", + "io.projectreactor:reactor-core", "jakarta.el:jakarta.el-api", "jakarta.servlet:jakarta.servlet-api", "javax.activation:javax.activation-api", + "javax.annotation:javax.annotation-api", "javax.el:javax.el-api", "javax.persistence:javax.persistence-api", "javax.validation:validation-api", @@ -2650,12 +2963,18 @@ "org.ow2.asm:asm", "org.ow2.asm:asm-commons", "org.ow2.asm:asm-tree", + "org.reactivestreams:reactive-streams", + "org.springframework.boot:spring-boot", + "org.springframework.boot:spring-boot-autoconfigure", + "org.springframework.cloud:spring-cloud-function-context", + "org.springframework.cloud:spring-cloud-function-core", "org.springframework:spring-aop", "org.springframework:spring-beans", "org.springframework:spring-context", "org.springframework:spring-core", "org.springframework:spring-expression", "org.springframework:spring-jcl", + "org.springframework:spring-messaging", "org.springframework:spring-test", "org.springframework:spring-web", "org.springframework:spring-webmvc", @@ -2705,6 +3024,11 @@ "io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor" ] }, + "io.projectreactor:reactor-core": { + "reactor.blockhound.integration.BlockHoundIntegration": [ + "reactor.core.scheduler.ReactorBlockHoundIntegration" + ] + }, "org.apache.logging.log4j:log4j-api": { "org.apache.logging.log4j.util.PropertySource": [ "org.apache.logging.log4j.util.EnvironmentPropertySource", diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt index 0dbb16794..6fd54ba3a 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt @@ -1,4 +1,3 @@ - /* * Copyright 2024 Code Intelligence GmbH * @@ -29,15 +28,19 @@ import java.lang.invoke.MethodHandle @Suppress("unused_parameter", "unused") object ExpressionLanguageInjection { /** - * Try to call the default constructor of the honeypot class. + * Try to call the el() method of the honeypot class. */ private const val EXPRESSION_LANGUAGE_ATTACK = "\${Byte.class.forName(\"$HONEYPOT_CLASS_NAME\").getMethod(\"el\").invoke(null)}" + private const val SPRING_EXPRESSION_LANGUAGE_ATTACK = "T($HONEYPOT_CLASS_NAME).el()" init { require(EXPRESSION_LANGUAGE_ATTACK.length <= 64) { "Expression language exploit must fit in a table of recent compares entry (64 bytes)" } + require(SPRING_EXPRESSION_LANGUAGE_ATTACK.length <= 64) { + "Expression language exploit must fit in a table of recent compares entry (64 bytes)" + } } @MethodHooks( @@ -102,4 +105,32 @@ object ExpressionLanguageInjection { val message = arguments[0] as String Jazzer.guideTowardsContainment(message, EXPRESSION_LANGUAGE_ATTACK, hookId) } + + /** + * Guides Spring Expression Language (SpEL) parsing towards payloads that execute RCE, enabling discovery of + * CVE-2022-22963-like bugs where SpEL evaluation is unexpectedly attacker-controlled. + */ + @MethodHooks( + MethodHook( + type = HookType.BEFORE, + targetClassName = "org.springframework.expression.spel.standard.SpelExpressionParser", + targetMethod = "parseRaw", + ), + MethodHook( + type = HookType.BEFORE, + targetClassName = "org.springframework.expression.common.TemplateAwareExpressionParser", + targetMethod = "parseExpression", + ), + ) + @JvmStatic + fun hookSpelParseExpression( + method: MethodHandle?, + thisObject: Any?, + arguments: Array, + hookId: Int, + ) { + if (arguments.isEmpty()) return + val expr = arguments[0] as? String ?: return + Jazzer.guideTowardsContainment(expr, SPRING_EXPRESSION_LANGUAGE_ATTACK, hookId) + } } diff --git a/sanitizers/src/test/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel b/sanitizers/src/test/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel index 583af67dc..0f6091d67 100644 --- a/sanitizers/src/test/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel +++ b/sanitizers/src/test/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel @@ -25,5 +25,7 @@ java_junit5_test( "@maven//:javax_validation_validation_api", "@maven//:org_junit_jupiter_junit_jupiter_api", "@maven//:org_junit_jupiter_junit_jupiter_params", + "@maven//:org_springframework_cloud_spring_cloud_function_context", + "@maven//:org_springframework_cloud_spring_cloud_function_core", ], ) diff --git a/sanitizers/src/test/java/com/code_intelligence/jazzer/sanitizers/HookBindingSanityTest.java b/sanitizers/src/test/java/com/code_intelligence/jazzer/sanitizers/HookBindingSanityTest.java index d7ab7f250..6dc6d1955 100644 --- a/sanitizers/src/test/java/com/code_intelligence/jazzer/sanitizers/HookBindingSanityTest.java +++ b/sanitizers/src/test/java/com/code_intelligence/jazzer/sanitizers/HookBindingSanityTest.java @@ -109,7 +109,11 @@ public boolean equals(Object o) { final Set SKIPPED_JDK_8 = Collections.unmodifiableSet( Stream.of( - new MethodRef("jakarta.el.ExpressionFactory"), // -> UnsupportedClassVersionError + new MethodRef( + "org.springframework.expression.common.TemplateAwareExpressionParser"), + new MethodRef( + "org.springframework.expression.spel.standard.SpelExpressionParser"), + new MethodRef("jakarta.el.ExpressionFactory"), new MethodRef("java.util.regex.Pattern$CharPredicate"), new MethodRef("javax.xml.xpath.XPath", "evaluateExpression", null), new MethodRef(