diff options
author | simonresch <65217285+simonresch@users.noreply.github.com> | 2021-10-19 10:17:16 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-19 08:17:16 +0000 |
commit | 7aa82bc17c0ed6844be4c196c4c5d21358d23f80 (patch) | |
tree | 86ccb2cc6765feafcdb026257a773735fe852a9c /sanitizers | |
parent | 37526a77f3e04d6a8391c6e9babfd1015354e3d1 (diff) | |
download | jazzer-api-7aa82bc17c0ed6844be4c196c4c5d21358d23f80.tar.gz |
Add sanitizer for expression language injection (#203)
* Add sanitizer for expresion language injection
* Extract honeypot class name variable
Diffstat (limited to 'sanitizers')
8 files changed, 190 insertions, 10 deletions
diff --git a/sanitizers/sanitizers.bzl b/sanitizers/sanitizers.bzl index fc93ddb5..488d4bd8 100644 --- a/sanitizers/sanitizers.bzl +++ b/sanitizers/sanitizers.bzl @@ -17,6 +17,7 @@ _sanitizer_package_prefix = "com.code_intelligence.jazzer.sanitizers." _sanitizer_class_names = [ "Deserialization", "ReflectiveCall", + "ExpressionLanguageInjection", ] SANITIZER_CLASSES = [_sanitizer_package_prefix + class_name for class_name in _sanitizer_class_names] diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Deserialization.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Deserialization.kt index af46b573..f6401dfd 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Deserialization.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Deserialization.kt @@ -32,11 +32,6 @@ import java.util.WeakHashMap @Suppress("unused_parameter") object Deserialization { - /** - * jaz.Zer is a honeypot class: All of its methods report a finding when called. - */ - private const val HONEYPOT_CLASS_NAME = "jaz.Zer" - private val OBJECT_INPUT_STREAM_HEADER = ObjectStreamConstants.STREAM_MAGIC.toBytes() + ObjectStreamConstants.STREAM_VERSION.toBytes() 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 new file mode 100644 index 00000000..9b1e8ca6 --- /dev/null +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt @@ -0,0 +1,83 @@ + +// Copyright 2021 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.code_intelligence.jazzer.sanitizers + +import com.code_intelligence.jazzer.api.HookType +import com.code_intelligence.jazzer.api.Jazzer +import com.code_intelligence.jazzer.api.MethodHook +import com.code_intelligence.jazzer.api.MethodHooks +import java.lang.invoke.MethodHandle + +/** + * Detects injectable inputs to an expression language interpreter which may lead to remote code execution. + */ +@Suppress("unused_parameter") +object ExpressionLanguageInjection { + + /** + * Try to call the default constructor of the honeypot class. + */ + private const val EXPRESSION_LANGUAGE_ATTACK = + "\${\"\".getClass().forName(\"$HONEYPOT_CLASS_NAME\").newInstance()}" + + @MethodHooks( + MethodHook( + type = HookType.BEFORE, + targetClassName = "javax.el.ExpressionFactory", + targetMethod = "createValueExpression", + ), + MethodHook( + type = HookType.BEFORE, + targetClassName = "javax.el.ExpressionFactory", + targetMethod = "createMethodExpression", + ), + ) + @JvmStatic + fun hookElExpressionFactory( + method: MethodHandle?, + thisObject: Any?, + arguments: Array<Any>, + hookId: Int + ) { + if (arguments[1] is String) { + val expression = arguments[1] as String + Jazzer.guideTowardsContainment(expression, EXPRESSION_LANGUAGE_ATTACK, hookId) + } + } + + // With default configurations the argument to + // ConstraintValidatorContext.buildConstraintViolationWithTemplate() will be evaluated by an + // Expression Language interpreter which allows arbitrary code execution if the attacker has + // control of the method argument. + // + // References: CVE-2018-16621 + // https://securitylab.github.com/research/bean-validation-RCE/ + @MethodHook( + type = HookType.BEFORE, + targetClassName = "javax.validation.ConstraintValidatorContext", + targetMethod = "buildConstraintViolationWithTemplate" + ) + @JvmStatic + fun hookBuildConstraintViolationWithTemplate( + method: MethodHandle?, + thisObject: Any?, + arguments: Array<Any>, + hookId: Int + ) { + val message = arguments[0] as String + Jazzer.guideTowardsContainment(message, EXPRESSION_LANGUAGE_ATTACK, hookId) + } +} diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt index d58f73f3..7842d879 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt @@ -25,11 +25,6 @@ import java.lang.invoke.MethodHandle @Suppress("unused_parameter") object ReflectiveCall { - /** - * jaz.Zer is a honeypot class: All of its methods report a finding when called. - */ - private const val HONEYPOT_CLASS_NAME = "jaz.Zer" - @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Class", targetMethod = "forName") @JvmStatic fun classForNameHook(method: MethodHandle?, alwaysNull: Any?, args: Array<Any?>, hookId: Int) { diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt index 57ba014e..3166773b 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt @@ -17,6 +17,11 @@ package com.code_intelligence.jazzer.sanitizers import com.code_intelligence.jazzer.api.Jazzer import java.io.InputStream +/** + * jaz.Zer is a honeypot class: All of its methods report a finding when called. + */ +const val HONEYPOT_CLASS_NAME = "jaz.Zer" + internal fun Short.toBytes(): ByteArray { return byteArrayOf( ((toInt() shr 8) and 0xFF).toByte(), diff --git a/sanitizers/src/test/java/com/example/BUILD.bazel b/sanitizers/src/test/java/com/example/BUILD.bazel index f86b6922..b2cb6543 100644 --- a/sanitizers/src/test/java/com/example/BUILD.bazel +++ b/sanitizers/src/test/java/com/example/BUILD.bazel @@ -15,3 +15,19 @@ java_fuzz_target_test( ], target_class = "com.example.ReflectiveCall", ) + +java_fuzz_target_test( + name = "ExpressionLanguageInjection", + srcs = [ + "ExpressionLanguageInjection.java", + "InsecureEmailValidator.java", + ], + target_class = "com.example.ExpressionLanguageInjection", + deps = [ + "@maven//:javax_el_javax_el_api", + "@maven//:javax_validation_validation_api", + "@maven//:javax_xml_bind_jaxb_api", + "@maven//:org_glassfish_javax_el", + "@maven//:org_hibernate_hibernate_validator", + ], +) diff --git a/sanitizers/src/test/java/com/example/ExpressionLanguageInjection.java b/sanitizers/src/test/java/com/example/ExpressionLanguageInjection.java new file mode 100644 index 00000000..e26a9117 --- /dev/null +++ b/sanitizers/src/test/java/com/example/ExpressionLanguageInjection.java @@ -0,0 +1,49 @@ +// Copyright 2021 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.code_intelligence.jazzer.api.FuzzedDataProvider; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.validation.*; + +class UserData { + public UserData(String email) { + this.email = email; + } + + @ValidEmailConstraint private String email; +} + +@Constraint(validatedBy = InsecureEmailValidator.class) +@Target({ElementType.METHOD, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@interface ValidEmailConstraint { + String message() default "Invalid email address"; + Class<?>[] groups() default {}; + Class<? extends Payload>[] payload() default {}; +} + +public class ExpressionLanguageInjection { + final private static Validator validator = + Validation.buildDefaultValidatorFactory().getValidator(); + + public static void fuzzerTestOneInput(FuzzedDataProvider data) { + UserData uncheckedUserData = new UserData(data.consumeRemainingAsString()); + validator.validate(uncheckedUserData); + } +} diff --git a/sanitizers/src/test/java/com/example/InsecureEmailValidator.java b/sanitizers/src/test/java/com/example/InsecureEmailValidator.java new file mode 100644 index 00000000..d61e888d --- /dev/null +++ b/sanitizers/src/test/java/com/example/InsecureEmailValidator.java @@ -0,0 +1,36 @@ +// Copyright 2021 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 static java.lang.String.format; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +public class InsecureEmailValidator implements ConstraintValidator<ValidEmailConstraint, String> { + @Override + public void initialize(ValidEmailConstraint email) {} + + @Override + public boolean isValid(String email, ConstraintValidatorContext cxt) { + if (email == null || !email.matches(".+@.+\\..+")) { + // Insecure: do not call buildConstraintViolationWithTemplate with untrusted data! + cxt.buildConstraintViolationWithTemplate(format("Invalid email address: %s", email)) + .addConstraintViolation(); + return false; + } + return true; + } +} |