Skip to content

Commit 9094131

Browse files
committed
feat: add jexl expression guidance hook
1 parent b819fcf commit 9094131

File tree

6 files changed

+73
-7
lines changed

6 files changed

+73
-7
lines changed

MODULE.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ TEST_MAVEN_ARTIFACTS = [
8585
"jakarta.validation:jakarta.validation-api:3.0.2",
8686
"javax.persistence:javax.persistence-api:2.2",
8787
"junit:junit:4.13.2",
88+
"org.apache.commons:commons-jexl:2.1.1",
8889
"org.assertj:assertj-core:3.27.6",
8990
"org.jacoco:org.jacoco.core:0.8.14",
9091
"org.mockito:mockito-core:5.20.0",

maven_install.json

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL",
3-
"__INPUT_ARTIFACTS_HASH": 910747468,
4-
"__RESOLVED_ARTIFACTS_HASH": 1315640420,
3+
"__INPUT_ARTIFACTS_HASH": -1638807165,
4+
"__RESOLVED_ARTIFACTS_HASH": -1440417829,
55
"conflict_resolution": {
66
"com.google.code.gson:gson:2.8.6": "com.google.code.gson:gson:2.8.9",
77
"com.google.j2objc:j2objc-annotations:2.8": "com.google.j2objc:j2objc-annotations:3.1",
@@ -166,9 +166,9 @@
166166
},
167167
"commons-logging:commons-logging": {
168168
"shasums": {
169-
"jar": "e94af49749384c11f5aa50e8d0f5fe679be771295b52030338d32843c980351e"
169+
"jar": "ce6f913cad1f0db3aad70186d65c5bc7ffcc9a99e3fe8e0b137312819f7c362f"
170170
},
171-
"version": "1.0.4"
171+
"version": "1.1.1"
172172
},
173173
"io.github.classgraph:classgraph": {
174174
"shasums": {
@@ -266,6 +266,12 @@
266266
},
267267
"version": "1.0-alpha2"
268268
},
269+
"org.apache.commons:commons-jexl": {
270+
"shasums": {
271+
"jar": "03c9a9fae5da78ce52c0bf24467cc37355b7e23196dff4839e2c0ff018a01306"
272+
},
273+
"version": "2.1.1"
274+
},
269275
"org.apache.commons:commons-lang3": {
270276
"shasums": {
271277
"jar": "4ee380259c068d1dbe9e84ab52186f2acd65de067ec09beff731fca1697fdb16"
@@ -733,6 +739,9 @@
733739
"junit:junit": [
734740
"org.hamcrest:hamcrest-core"
735741
],
742+
"org.apache.commons:commons-jexl": [
743+
"commons-logging:commons-logging"
744+
],
736745
"org.apache.commons:commons-text": [
737746
"org.apache.commons:commons-lang3"
738747
],
@@ -1478,6 +1487,14 @@
14781487
"org.apache.commons.imaging.internal",
14791488
"org.apache.commons.imaging.palette"
14801489
],
1490+
"org.apache.commons:commons-jexl": [
1491+
"org.apache.commons.jexl2",
1492+
"org.apache.commons.jexl2.internal",
1493+
"org.apache.commons.jexl2.internal.introspection",
1494+
"org.apache.commons.jexl2.introspection",
1495+
"org.apache.commons.jexl2.parser",
1496+
"org.apache.commons.jexl2.scripting"
1497+
],
14811498
"org.apache.commons:commons-lang3": [
14821499
"org.apache.commons.lang3",
14831500
"org.apache.commons.lang3.arch",
@@ -2722,6 +2739,7 @@
27222739
"net.jodah:typetools",
27232740
"net.sf.jopt-simple:jopt-simple",
27242741
"org.apache.commons:commons-imaging",
2742+
"org.apache.commons:commons-jexl",
27252743
"org.apache.commons:commons-lang3",
27262744
"org.apache.commons:commons-math3",
27272745
"org.apache.commons:commons-text",
@@ -2831,6 +2849,11 @@
28312849
"reactor.core.scheduler.ReactorBlockHoundIntegration"
28322850
]
28332851
},
2852+
"org.apache.commons:commons-jexl": {
2853+
"javax.script.ScriptEngineFactory": [
2854+
"org.apache.commons.jexl2.scripting.JexlScriptEngineFactory"
2855+
]
2856+
},
28342857
"org.apache.logging.log4j:log4j-api": {
28352858
"org.apache.logging.log4j.util.PropertySource": [
28362859
"org.apache.logging.log4j.util.EnvironmentPropertySource",

sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ object ExpressionLanguageInjection {
3333
private const val EXPRESSION_LANGUAGE_ATTACK =
3434
"\${Byte.class.forName(\"$HONEYPOT_CLASS_NAME\").getMethod(\"el\").invoke(null)}"
3535
private const val SPRING_EXPRESSION_LANGUAGE_ATTACK = "T($HONEYPOT_CLASS_NAME).el()"
36-
private const val ELPROCESSOR_LANGUAGE_ATTACK =
36+
private const val ELPROCESSOR_JEXL_LANGUAGE_ATTACK =
3737
"\"\".getClass().forName(\"$HONEYPOT_CLASS_NAME\").getMethod(\"el\").invoke(null)"
3838

3939
init {
@@ -43,7 +43,7 @@ object ExpressionLanguageInjection {
4343
require(SPRING_EXPRESSION_LANGUAGE_ATTACK.length <= 64) {
4444
"Expression language exploit must fit in a table of recent compares entry (64 bytes)"
4545
}
46-
require(ELPROCESSOR_LANGUAGE_ATTACK.length <= 64) {
46+
require(ELPROCESSOR_JEXL_LANGUAGE_ATTACK.length <= 64) {
4747
"Expression language exploit must fit in a table of recent compares entry (64 bytes)"
4848
}
4949
}
@@ -108,7 +108,7 @@ object ExpressionLanguageInjection {
108108
return
109109
}
110110
val message = arguments[0] as String
111-
Jazzer.guideTowardsContainment(message, ELPROCESSOR_LANGUAGE_ATTACK, hookId)
111+
Jazzer.guideTowardsContainment(message, ELPROCESSOR_JEXL_LANGUAGE_ATTACK, hookId)
112112
}
113113

114114
// With default configurations the argument to
@@ -171,4 +171,26 @@ object ExpressionLanguageInjection {
171171
val expr = arguments[0] as? String ?: return
172172
Jazzer.guideTowardsContainment(expr, SPRING_EXPRESSION_LANGUAGE_ATTACK, hookId)
173173
}
174+
175+
/**
176+
* Guides JEXL expression parsing towards payloads that execute RCE. Note that `parse` is
177+
* triggered by vulnerable public methods.
178+
*/
179+
@MethodHook(
180+
type = HookType.BEFORE,
181+
targetClassName = "org.apache.commons.jexl2.JexlEngine",
182+
targetMethod = "parse",
183+
additionalClassesToHook = ["org.apache.commons.jexl2.JexlEngine"],
184+
)
185+
@JvmStatic
186+
fun hookJexlParse(
187+
method: MethodHandle?,
188+
thisObject: Any?,
189+
arguments: Array<Any>,
190+
hookId: Int,
191+
) {
192+
if (arguments.isEmpty()) return
193+
val expr = arguments[0] as? CharSequence ?: return
194+
Jazzer.guideTowardsContainment(expr.toString(), ELPROCESSOR_JEXL_LANGUAGE_ATTACK, hookId)
195+
}
174196
}

sanitizers/src/test/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ java_junit5_test(
2424
"@maven//:javax_el_javax_el_api",
2525
"@maven//:javax_persistence_javax_persistence_api",
2626
"@maven//:javax_validation_validation_api",
27+
"@maven//:org_apache_commons_commons_jexl",
2728
"@maven//:org_junit_jupiter_junit_jupiter_api",
2829
"@maven//:org_junit_jupiter_junit_jupiter_params",
2930
"@maven//:org_springframework_cloud_spring_cloud_function_context",

sanitizers/src/test/java/com/example/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,13 @@ java_fuzz_target_test(
6464
"//sanitizers/src/test/java/com/example/el:ExpressionLanguageExample",
6565
"@maven//:javax_el_javax_el_api",
6666
"@maven//:javax_validation_validation_api",
67+
"@maven//:org_apache_commons_commons_jexl",
6768
"@maven//:org_junit_jupiter_junit_jupiter_api",
6869
],
6970
) for method in [
7071
"fuzzValidator",
7172
"fuzzEval",
73+
"fuzzJexlExpression",
7274
]]
7375

7476
java_fuzz_target_test(

sanitizers/src/test/java/com/example/ExpressionLanguageInjection.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525
import javax.el.ELProcessor;
2626
import javax.validation.Validation;
2727
import javax.validation.Validator;
28+
import org.apache.commons.jexl2.Expression;
29+
import org.apache.commons.jexl2.JexlContext;
30+
import org.apache.commons.jexl2.JexlEngine;
31+
import org.apache.commons.jexl2.JexlException;
32+
import org.apache.commons.jexl2.MapContext;
2833
import org.junit.jupiter.api.BeforeEach;
2934

3035
public class ExpressionLanguageInjection {
@@ -53,4 +58,16 @@ void fuzzEval(@NotNull String data) {
5358
| ArithmeticException ignored) {
5459
}
5560
}
61+
62+
@FuzzTest
63+
void fuzzJexlExpression(@NotNull String data) {
64+
JexlEngine jexl = new JexlEngine();
65+
JexlContext context = new MapContext();
66+
67+
try {
68+
Expression expr = jexl.createExpression(data);
69+
expr.evaluate(context);
70+
} catch (JexlException | StringIndexOutOfBoundsException ignored) {
71+
}
72+
}
5673
}

0 commit comments

Comments
 (0)