diff options
10 files changed, 297 insertions, 12 deletions
@@ -25,4 +25,9 @@ MAVEN_ARTIFACTS = [ "com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.12.1", "com.alibaba:fastjson:1.2.75", "com.beust:klaxon:5.5", + "javax.validation:validation-api:2.0.1.Final", + "javax.xml.bind:jaxb-api:2.3.1", + "javax.el:javax.el-api:3.0.1-b06", + "org.hibernate:hibernate-validator:5.2.4.Final", + "org.glassfish:javax.el:3.0.1-b06", ] diff --git a/maven_install.json b/maven_install.json index 0cf7b858..e182bff6 100644 --- a/maven_install.json +++ b/maven_install.json @@ -1,8 +1,8 @@ { "dependency_tree": { "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL", - "__INPUT_ARTIFACTS_HASH": 1204355925, - "__RESOLVED_ARTIFACTS_HASH": 897458754, + "__INPUT_ARTIFACTS_HASH": -1154277281, + "__RESOLVED_ARTIFACTS_HASH": -451611894, "conflict_resolution": {}, "dependencies": [ { @@ -93,6 +93,17 @@ "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.12.1/jackson-dataformat-cbor-2.12.1.jar" }, { + "coord": "com.fasterxml:classmate:1.1.0", + "dependencies": [], + "directDependencies": [], + "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/classmate/1.1.0/classmate-1.1.0.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/fasterxml/classmate/1.1.0/classmate-1.1.0.jar" + ], + "sha256": "610d23db8ece7268e93930562d89b91546c79fc80f3966baf433e5e93110b118", + "url": "https://repo1.maven.org/maven2/com/fasterxml/classmate/1.1.0/classmate-1.1.0.jar" + }, + { "coord": "com.google.code.gson:gson:2.8.6", "dependencies": [], "directDependencies": [], @@ -115,6 +126,54 @@ "url": "https://repo1.maven.org/maven2/com/mikesamuel/json-sanitizer/1.2.1/json-sanitizer-1.2.1.jar" }, { + "coord": "javax.activation:javax.activation-api:1.2.0", + "dependencies": [], + "directDependencies": [], + "file": "v1/https/repo1.maven.org/maven2/javax/activation/javax.activation-api/1.2.0/javax.activation-api-1.2.0.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/javax/activation/javax.activation-api/1.2.0/javax.activation-api-1.2.0.jar" + ], + "sha256": "43fdef0b5b6ceb31b0424b208b930c74ab58fac2ceeb7b3f6fd3aeb8b5ca4393", + "url": "https://repo1.maven.org/maven2/javax/activation/javax.activation-api/1.2.0/javax.activation-api-1.2.0.jar" + }, + { + "coord": "javax.el:javax.el-api:3.0.1-b06", + "dependencies": [], + "directDependencies": [], + "file": "v1/https/repo1.maven.org/maven2/javax/el/javax.el-api/3.0.1-b06/javax.el-api-3.0.1-b06.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/javax/el/javax.el-api/3.0.1-b06/javax.el-api-3.0.1-b06.jar" + ], + "sha256": "0b46b36709ecbb9791ac4ba44d16125b9d65b576112afdaaa286052b6e498bc4", + "url": "https://repo1.maven.org/maven2/javax/el/javax.el-api/3.0.1-b06/javax.el-api-3.0.1-b06.jar" + }, + { + "coord": "javax.validation:validation-api:2.0.1.Final", + "dependencies": [], + "directDependencies": [], + "file": "v1/https/repo1.maven.org/maven2/javax/validation/validation-api/2.0.1.Final/validation-api-2.0.1.Final.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/javax/validation/validation-api/2.0.1.Final/validation-api-2.0.1.Final.jar" + ], + "sha256": "9873b46df1833c9ee8f5bc1ff6853375115dadd8897bcb5a0dffb5848835ee6c", + "url": "https://repo1.maven.org/maven2/javax/validation/validation-api/2.0.1.Final/validation-api-2.0.1.Final.jar" + }, + { + "coord": "javax.xml.bind:jaxb-api:2.3.1", + "dependencies": [ + "javax.activation:javax.activation-api:1.2.0" + ], + "directDependencies": [ + "javax.activation:javax.activation-api:1.2.0" + ], + "file": "v1/https/repo1.maven.org/maven2/javax/xml/bind/jaxb-api/2.3.1/jaxb-api-2.3.1.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/javax/xml/bind/jaxb-api/2.3.1/jaxb-api-2.3.1.jar" + ], + "sha256": "88b955a0df57880a26a74708bc34f74dcaf8ebf4e78843a28b50eae945732b06", + "url": "https://repo1.maven.org/maven2/javax/xml/bind/jaxb-api/2.3.1/jaxb-api-2.3.1.jar" + }, + { "coord": "junit:junit:4.12", "dependencies": [ "org.hamcrest:hamcrest-core:1.3" @@ -141,6 +200,17 @@ "url": "https://repo1.maven.org/maven2/org/apache/commons/commons-imaging/1.0-alpha2/commons-imaging-1.0-alpha2.jar" }, { + "coord": "org.glassfish:javax.el:3.0.1-b06", + "dependencies": [], + "directDependencies": [], + "file": "v1/https/repo1.maven.org/maven2/org/glassfish/javax.el/3.0.1-b06/javax.el-3.0.1-b06.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/org/glassfish/javax.el/3.0.1-b06/javax.el-3.0.1-b06.jar" + ], + "sha256": "c255fe3ff4d7e491caf92c10c497f3c77d19acc4832d9bd2e80180d168fcedd2", + "url": "https://repo1.maven.org/maven2/org/glassfish/javax.el/3.0.1-b06/javax.el-3.0.1-b06.jar" + }, + { "coord": "org.hamcrest:hamcrest-core:1.3", "dependencies": [], "directDependencies": [], @@ -152,6 +222,36 @@ "url": "https://repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar" }, { + "coord": "org.hibernate:hibernate-validator:5.2.4.Final", + "dependencies": [ + "org.jboss.logging:jboss-logging:3.2.1.Final", + "com.fasterxml:classmate:1.1.0", + "javax.validation:validation-api:2.0.1.Final" + ], + "directDependencies": [ + "com.fasterxml:classmate:1.1.0", + "javax.validation:validation-api:2.0.1.Final", + "org.jboss.logging:jboss-logging:3.2.1.Final" + ], + "file": "v1/https/repo1.maven.org/maven2/org/hibernate/hibernate-validator/5.2.4.Final/hibernate-validator-5.2.4.Final.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/org/hibernate/hibernate-validator/5.2.4.Final/hibernate-validator-5.2.4.Final.jar" + ], + "sha256": "fc7e2ed4079859f61390932a4f4cd5b2447e1ebc77d4915badb1a0655588697a", + "url": "https://repo1.maven.org/maven2/org/hibernate/hibernate-validator/5.2.4.Final/hibernate-validator-5.2.4.Final.jar" + }, + { + "coord": "org.jboss.logging:jboss-logging:3.2.1.Final", + "dependencies": [], + "directDependencies": [], + "file": "v1/https/repo1.maven.org/maven2/org/jboss/logging/jboss-logging/3.2.1.Final/jboss-logging-3.2.1.Final.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/org/jboss/logging/jboss-logging/3.2.1.Final/jboss-logging-3.2.1.Final.jar" + ], + "sha256": "a3b0ffa8ae2b2f2387ebdfdce29086d3955d2a46ce7da802c2ba6ae47fa2f1bf", + "url": "https://repo1.maven.org/maven2/org/jboss/logging/jboss-logging/3.2.1.Final/jboss-logging-3.2.1.Final.jar" + }, + { "coord": "org.jetbrains.kotlin:kotlin-reflect:1.4.31", "dependencies": [ "org.jetbrains.kotlin:kotlin-stdlib:1.4.31", 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; + } +} |