aboutsummaryrefslogtreecommitdiff
path: root/sanitizers
diff options
context:
space:
mode:
authorsimonresch <65217285+simonresch@users.noreply.github.com>2021-10-19 10:17:16 +0200
committerGitHub <noreply@github.com>2021-10-19 08:17:16 +0000
commit7aa82bc17c0ed6844be4c196c4c5d21358d23f80 (patch)
tree86ccb2cc6765feafcdb026257a773735fe852a9c /sanitizers
parent37526a77f3e04d6a8391c6e9babfd1015354e3d1 (diff)
downloadjazzer-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')
-rw-r--r--sanitizers/sanitizers.bzl1
-rw-r--r--sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Deserialization.kt5
-rw-r--r--sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt83
-rw-r--r--sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt5
-rw-r--r--sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt5
-rw-r--r--sanitizers/src/test/java/com/example/BUILD.bazel16
-rw-r--r--sanitizers/src/test/java/com/example/ExpressionLanguageInjection.java49
-rw-r--r--sanitizers/src/test/java/com/example/InsecureEmailValidator.java36
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;
+ }
+}