diff --git a/MODULE.bazel b/MODULE.bazel index f8ee18120..382f5531e 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -85,6 +85,7 @@ TEST_MAVEN_ARTIFACTS = [ "jakarta.validation:jakarta.validation-api:3.0.2", "javax.persistence:javax.persistence-api:2.2", "junit:junit:4.13.2", + "ognl:ognl:3.3.0", "org.apache.commons:commons-jexl:2.1.1", "org.assertj:assertj-core:3.27.6", "org.jacoco:org.jacoco.core:0.8.14", diff --git a/maven_install.json b/maven_install.json index e6dc6c600..fef74579a 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": 1624596153, - "__RESOLVED_ARTIFACTS_HASH": 1478602772, + "__INPUT_ARTIFACTS_HASH": -620116506, + "__RESOLVED_ARTIFACTS_HASH": 1157173568, "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", @@ -260,6 +260,12 @@ }, "version": "5.0.4" }, + "ognl:ognl": { + "shasums": { + "jar": "5ee6714ac10e66deabd80c15f3f7d4d733d5a7c99c0904d2e7b207c72221b475" + }, + "version": "3.3.0" + }, "org.apache.commons:commons-imaging": { "shasums": { "jar": "64d649007364d70dcab24a1f895646e6976f5e2b339ba73a4af20642d041666a" @@ -446,6 +452,12 @@ }, "version": "0.8.14" }, + "org.javassist:javassist": { + "shasums": { + "jar": "57d0a9e9286f82f4eaa851125186997f811befce0e2060ff0a15a77f5a9dd9a7" + }, + "version": "3.28.0-GA" + }, "org.jboss.logging:jboss-logging": { "shasums": { "jar": "a3b0ffa8ae2b2f2387ebdfdce29086d3955d2a46ce7da802c2ba6ae47fa2f1bf" @@ -745,6 +757,9 @@ "junit:junit": [ "org.hamcrest:hamcrest-core" ], + "ognl:ognl": [ + "org.javassist:javassist" + ], "org.apache.commons:commons-jexl": [ "commons-logging:commons-logging" ], @@ -1450,6 +1465,12 @@ "joptsimple.internal", "joptsimple.util" ], + "ognl:ognl": [ + "ognl", + "ognl.enhance", + "ognl.internal", + "ognl.security" + ], "org.apache.commons:commons-imaging": [ "org.apache.commons.imaging", "org.apache.commons.imaging.color", @@ -1916,6 +1937,25 @@ "org.jacoco.core.runtime", "org.jacoco.core.tools" ], + "org.javassist:javassist": [ + "javassist", + "javassist.bytecode", + "javassist.bytecode.analysis", + "javassist.bytecode.annotation", + "javassist.bytecode.stackmap", + "javassist.compiler", + "javassist.compiler.ast", + "javassist.convert", + "javassist.expr", + "javassist.runtime", + "javassist.scopedpool", + "javassist.tools", + "javassist.tools.reflect", + "javassist.tools.rmi", + "javassist.tools.web", + "javassist.util", + "javassist.util.proxy" + ], "org.jboss.logging:jboss-logging": [ "org.jboss.logging" ], @@ -2772,6 +2812,7 @@ "net.bytebuddy:byte-buddy-agent", "net.jodah:typetools", "net.sf.jopt-simple:jopt-simple", + "ognl:ognl", "org.apache.commons:commons-imaging", "org.apache.commons:commons-jexl", "org.apache.commons:commons-lang3", @@ -2803,6 +2844,7 @@ "org.hamcrest:hamcrest-core", "org.hibernate:hibernate-validator", "org.jacoco:org.jacoco.core", + "org.javassist:javassist", "org.jboss.logging:jboss-logging", "org.jetbrains.kotlin:kotlin-reflect", "org.jetbrains.kotlin:kotlin-stdlib", 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 11ac6df04..f76513153 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 OGNL_LANGUAGE_ATTACK = "@$HONEYPOT_CLASS_NAME@el()" private const val MVEL_ATTACK = "Runtime.getRuntime().exec(\"jazze\")" init { @@ -47,6 +48,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(OGNL_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)" } @@ -244,4 +248,65 @@ object ExpressionLanguageInjection { } Jazzer.guideTowardsContainment(message, MVEL_ATTACK, hookId) } + + @MethodHooks( + MethodHook( + type = HookType.BEFORE, + targetClassName = "ognl.Ognl", + targetMethod = "parseExpression", + ), + MethodHook( + type = HookType.BEFORE, + targetClassName = "ognl.Ognl", + targetMethod = "getValue", + ), + MethodHook( + type = HookType.BEFORE, + targetClassName = "ognl.Ognl", + targetMethod = "setValue", + ), + MethodHook( + type = HookType.BEFORE, + targetClassName = "ognl.Ognl", + targetMethod = "isConstant", + ), + MethodHook( + type = HookType.BEFORE, + targetClassName = "ognl.Ognl", + targetMethod = "isSimpleProperty", + ), + MethodHook( + type = HookType.BEFORE, + targetClassName = "ognl.Ognl", + targetMethod = "isSimpleNavigationChain", + ), + ) + @JvmStatic + fun hookParseOgnlExpression( + method: MethodHandle?, + thisObject: Any?, + arguments: Array, + hookId: Int, + ) { + if (arguments.isEmpty()) return + val expr = arguments[0] as? String ?: return + Jazzer.guideTowardsContainment(expr, OGNL_LANGUAGE_ATTACK, hookId) + } + + @MethodHook( + type = HookType.BEFORE, + targetClassName = "ognl.Ognl", + targetMethod = "compileExpression", + ) + @JvmStatic + fun hookCompileOgnlExpression( + method: MethodHandle?, + thisObject: Any?, + arguments: Array, + hookId: Int, + ) { + if (arguments.size != 3) return + val expr = arguments[2] as? String ?: return + Jazzer.guideTowardsContainment(expr, OGNL_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 aca9145fb..a9bc80797 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 @@ -24,6 +24,7 @@ java_junit5_test( "@maven//:javax_el_javax_el_api", "@maven//:javax_persistence_javax_persistence_api", "@maven//:javax_validation_validation_api", + "@maven//:ognl_ognl", "@maven//:org_apache_commons_commons_jexl", "@maven//:org_junit_jupiter_junit_jupiter_api", "@maven//:org_junit_jupiter_junit_jupiter_params", diff --git a/sanitizers/src/test/java/com/example/BUILD.bazel b/sanitizers/src/test/java/com/example/BUILD.bazel index 9246f8a02..6df1c3dd2 100644 --- a/sanitizers/src/test/java/com/example/BUILD.bazel +++ b/sanitizers/src/test/java/com/example/BUILD.bazel @@ -67,6 +67,7 @@ java_fuzz_target_test( "//sanitizers/src/test/java/com/example/el:ExpressionLanguageExample", "@maven//:javax_el_javax_el_api", "@maven//:javax_validation_validation_api", + "@maven//:ognl_ognl", "@maven//:org_apache_commons_commons_jexl", "@maven//:org_junit_jupiter_junit_jupiter_api", "@maven//:org_mvel_mvel2", @@ -76,6 +77,7 @@ java_fuzz_target_test( "fuzzEval", "fuzzJexlExpression", "fuzzMVELExpression", + "fuzzOgnlExpression", ]] 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 cb3d8345e..f4c0575ce 100644 --- a/sanitizers/src/test/java/com/example/ExpressionLanguageInjection.java +++ b/sanitizers/src/test/java/com/example/ExpressionLanguageInjection.java @@ -25,6 +25,7 @@ import javax.el.ELProcessor; import javax.validation.Validation; import javax.validation.Validator; +import ognl.Ognl; import org.apache.commons.jexl2.Expression; import org.apache.commons.jexl2.JexlContext; import org.apache.commons.jexl2.JexlEngine; @@ -79,4 +80,14 @@ void fuzzMVELExpression(@NotNull String data) { } catch (Throwable ignored) { } } + + @FuzzTest + void fuzzOgnlExpression(@NotNull String data) { + try { + Object expression = Ognl.parseExpression(data); + Object root = new Object(); + Ognl.getValue(expression, root); + } catch (Throwable ignored) { + } + } }