From 083058b4319ed96c5c438b0bc31c740b6b2b873f Mon Sep 17 00:00:00 2001 From: Simon Resch Date: Thu, 30 Oct 2025 10:40:38 +0100 Subject: [PATCH] feat: add MVEL guidance hook Hook to guide inputs to MVEL interpreter towards performing OS commands which are detected by the OS command injection sanitizer. --- MODULE.bazel | 1 + maven_install.json | 44 +++++++++++++++- .../sanitizers/ExpressionLanguageInjection.kt | 51 +++++++++++++++++++ .../jazzer/sanitizers/BUILD.bazel | 1 + .../src/test/java/com/example/BUILD.bazel | 7 ++- .../example/ExpressionLanguageInjection.java | 9 ++++ 6 files changed, 110 insertions(+), 3 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index cbe7ca198..f8ee18120 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -89,6 +89,7 @@ TEST_MAVEN_ARTIFACTS = [ "org.assertj:assertj-core:3.27.6", "org.jacoco:org.jacoco.core:0.8.14", "org.mockito:mockito-core:5.20.0", + "org.mvel:mvel2:2.5.2.Final", "org.openjdk.jmh:jmh-core:1.37", "org.openjdk.jmh:jmh-generator-annprocess:1.37", ] diff --git a/maven_install.json b/maven_install.json index 1a38ac8d0..e6dc6c600 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": -1638807165, - "__RESOLVED_ARTIFACTS_HASH": -1440417829, + "__INPUT_ARTIFACTS_HASH": 1624596153, + "__RESOLVED_ARTIFACTS_HASH": 1478602772, "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", @@ -536,6 +536,12 @@ }, "version": "5.20.0" }, + "org.mvel:mvel2": { + "shasums": { + "jar": "77d14116dfad5259aa3c21e177fb5455b2c86f4873f49828fdb6000ebe77660d" + }, + "version": "2.5.2.Final" + }, "org.objenesis:objenesis": { "shasums": { "jar": "02dfd0b0439a5591e35b708ed2f5474eb0948f53abf74637e959b8e4ef69bfeb" @@ -2164,6 +2170,34 @@ "org.mockito.stubbing", "org.mockito.verification" ], + "org.mvel:mvel2": [ + "org.mvel2", + "org.mvel2.asm", + "org.mvel2.asm.signature", + "org.mvel2.ast", + "org.mvel2.compiler", + "org.mvel2.conversion", + "org.mvel2.debug", + "org.mvel2.integration", + "org.mvel2.integration.impl", + "org.mvel2.jsr223", + "org.mvel2.math", + "org.mvel2.optimizers", + "org.mvel2.optimizers.dynamic", + "org.mvel2.optimizers.impl.asm", + "org.mvel2.optimizers.impl.refl", + "org.mvel2.optimizers.impl.refl.collection", + "org.mvel2.optimizers.impl.refl.nodes", + "org.mvel2.sh", + "org.mvel2.sh.command.basic", + "org.mvel2.sh.command.file", + "org.mvel2.sh.text", + "org.mvel2.templates", + "org.mvel2.templates.res", + "org.mvel2.templates.util", + "org.mvel2.templates.util.io", + "org.mvel2.util" + ], "org.objenesis:objenesis": [ "org.objenesis", "org.objenesis.instantiator", @@ -2784,6 +2818,7 @@ "org.junit.platform:junit-platform-reporting", "org.junit.platform:junit-platform-testkit", "org.mockito:mockito-core", + "org.mvel:mvel2", "org.objenesis:objenesis", "org.openjdk.jmh:jmh-core", "org.openjdk.jmh:jmh-generator-annprocess", @@ -2945,6 +2980,11 @@ "org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener" ] }, + "org.mvel:mvel2": { + "javax.script.ScriptEngineFactory": [ + "org.mvel2.jsr223.MvelScriptEngineFactory" + ] + }, "org.openjdk.jmh:jmh-generator-annprocess": { "javax.annotation.processing.Processor": [ "org.openjdk.jmh.generators.BenchmarkProcessor" 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 7c6e570b2..11ac6df04 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 @@ -35,6 +35,7 @@ object ExpressionLanguageInjection { private const val SPRING_EXPRESSION_LANGUAGE_ATTACK = "T($HONEYPOT_CLASS_NAME).el()" private const val ELPROCESSOR_JEXL_LANGUAGE_ATTACK = "\"\".getClass().forName(\"$HONEYPOT_CLASS_NAME\").getMethod(\"el\").invoke(null)" + private const val MVEL_ATTACK = "Runtime.getRuntime().exec(\"jazze\")" init { require(EXPRESSION_LANGUAGE_ATTACK.length <= 64) { @@ -46,6 +47,9 @@ object ExpressionLanguageInjection { require(ELPROCESSOR_JEXL_LANGUAGE_ATTACK.length <= 64) { "Expression language exploit must fit in a table of recent compares entry (64 bytes)" } + require(MVEL_ATTACK.length <= 64) { + "MVEL exploit must fit in a table of recent compares entry (64 bytes)" + } } @MethodHooks( @@ -193,4 +197,51 @@ object ExpressionLanguageInjection { val expr = arguments[0] as? CharSequence ?: return Jazzer.guideTowardsContainment(expr.toString(), ELPROCESSOR_JEXL_LANGUAGE_ATTACK, hookId) } + + @MethodHook( + type = HookType.BEFORE, + targetClassName = "org.mvel2.MVEL", + targetMethod = "eval", + ) + @MethodHook( + type = HookType.BEFORE, + targetClassName = "org.mvel2.MVEL", + targetMethod = "evalToString", + ) + @MethodHook( + type = HookType.BEFORE, + targetClassName = "org.mvel2.MVEL", + targetMethod = "evalToBoolean", + ) + @MethodHook( + type = HookType.BEFORE, + targetClassName = "org.mvel2.MVEL", + targetMethod = "compileExpression", + ) + @MethodHook( + type = HookType.BEFORE, + targetClassName = "org.mvel2.MVEL", + targetMethod = "compileGetExpression", + ) + @MethodHook( + type = HookType.BEFORE, + targetClassName = "org.mvel2.MVEL", + targetMethod = "compileSetExpression", + ) + @JvmStatic + fun mvelEval( + method: MethodHandle?, + thisObject: Any?, + arguments: Array, + hookId: Int, + ) { + if (arguments.isEmpty()) return + val message = + when (val arg0 = arguments[0]) { + is String -> arg0 + is CharArray -> String(arg0) + else -> throw IllegalArgumentException("Unexpected type for arguments[0] in ExpressionLanguageInjection hook") + } + Jazzer.guideTowardsContainment(message, MVEL_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 8fd528b2a..aca9145fb 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 @@ -27,6 +27,7 @@ java_junit5_test( "@maven//:org_apache_commons_commons_jexl", "@maven//:org_junit_jupiter_junit_jupiter_api", "@maven//:org_junit_jupiter_junit_jupiter_params", + "@maven//:org_mvel_mvel2", "@maven//:org_springframework_cloud_spring_cloud_function_context", "@maven//:org_springframework_cloud_spring_cloud_function_core", ], diff --git a/sanitizers/src/test/java/com/example/BUILD.bazel b/sanitizers/src/test/java/com/example/BUILD.bazel index 1507edaff..9246f8a02 100644 --- a/sanitizers/src/test/java/com/example/BUILD.bazel +++ b/sanitizers/src/test/java/com/example/BUILD.bazel @@ -50,7 +50,10 @@ java_fuzz_target_test( srcs = [ "ExpressionLanguageInjection.java", ], - allowed_findings = ["com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh"], + allowed_findings = [ + "com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh", + "com.code_intelligence.jazzer.api.FuzzerSecurityIssueCritical", + ], tags = ["dangerous"], target_class = "com.example.ExpressionLanguageInjection", target_method = method, @@ -66,11 +69,13 @@ java_fuzz_target_test( "@maven//:javax_validation_validation_api", "@maven//:org_apache_commons_commons_jexl", "@maven//:org_junit_jupiter_junit_jupiter_api", + "@maven//:org_mvel_mvel2", ], ) for method in [ "fuzzValidator", "fuzzEval", "fuzzJexlExpression", + "fuzzMVELExpression", ]] java_fuzz_target_test( diff --git a/sanitizers/src/test/java/com/example/ExpressionLanguageInjection.java b/sanitizers/src/test/java/com/example/ExpressionLanguageInjection.java index 40aad2b5f..cb3d8345e 100644 --- a/sanitizers/src/test/java/com/example/ExpressionLanguageInjection.java +++ b/sanitizers/src/test/java/com/example/ExpressionLanguageInjection.java @@ -31,6 +31,7 @@ import org.apache.commons.jexl2.JexlException; import org.apache.commons.jexl2.MapContext; import org.junit.jupiter.api.BeforeEach; +import org.mvel2.MVEL; public class ExpressionLanguageInjection { private static final Validator validator = @@ -70,4 +71,12 @@ void fuzzJexlExpression(@NotNull String data) { } catch (JexlException | StringIndexOutOfBoundsException ignored) { } } + + @FuzzTest + void fuzzMVELExpression(@NotNull String data) { + try { + MVEL.executeExpression(MVEL.compileExpression(data)); + } catch (Throwable ignored) { + } + } }