-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Java: Query for detecting Jakarta Expression Language injections #5471
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- Added JakartaExpressionInjection.ql - Added a qhelp file with examples
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mostly looked for typos, but I hope the review comments are useful nonetheless.
The maintainers will probably be able to perform a better content-wise review.
And slightly unrelated, in case you did not know, GitHub also supports multi-line source code links, e.g. (random example):
Lines 5 to 6 in 34e2562
| test: |
So you could adjust your comment on the OpenFaces GitHub issue.
java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.qhelp
Outdated
Show resolved
Hide resolved
java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.qhelp
Outdated
Show resolved
Hide resolved
java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.qhelp
Outdated
Show resolved
Hide resolved
java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.qhelp
Outdated
Show resolved
Hide resolved
java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.qhelp
Outdated
Show resolved
Hide resolved
| Apache Foundation: | ||
| <a href="https://commons.apache.org/dormant/commons-el/">Apache Commons EL</a> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might not actually be a good idea to refer to a framework in "dormant" state last released in 2003.
Maybe instead refer to JEXL?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, makes sense. I am not sure that JEXL implements the Jakarta EL. I just removed the link.
| m.hasName("invoke") and | ||
| ma.getQualifier() = taintFrom | ||
| or | ||
| m.getDeclaringType() instanceof ELProcessor and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
defineFunction looks also somewhat dangerous. If I understand it correctly it allows you to defined a function referring to any static method (assuming className or method is user-controlled).
(Though based on the source code it looks like it cannot be used to initialize a gadget class without further actions.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it definitely looks somewhat dangerous. If someone can control class and method names, then it can call the method via the specified function name. Then, the attacker has two options. The first option is to use the function name in an expression that the attacker controls. That's not interesting because the attacker can already run arbitrary expression. The second option is to wait for the function to be called later. The attacker will need to control the arguments. Or, maybe the function has no arguments. This sounds a bit difficult to exploit although it doesn't seem to be impossible. Anyway, the risk looks pretty low to me. If no objections, let's not add defineFunction as a sink. Please let me know if you find a good scenario how defineFunction can be exploited and I'll update the query. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking more of the second option. The application allows the user to specify method and maybe also class name and then calls the method itself . For some dangerous methods the argument value would not even matter, e.g. for System.exit(int) regardless of what int value the application uses and what its intended meaning is, it is a valid argument for the exit call and will stop the JVM.
Though such a scenario is rather unlikely. On the other hand if the user is able to affect the class name of defineFunction in any way, then this could already be a security concern, regardless of how likely it is. My feeling is that false positives are rather unlikely there.
| m.hasName("invoke") and | ||
| ma.getQualifier() = taintFrom | ||
| or | ||
| m.getDeclaringType() instanceof ELProcessor and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this also cover setVariable? It does not evaluate the expression immediately, but maybe a user is able to replace a variable which is then later referenced by the application. (though I am not completely sure regarding this)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about covering setVariable in this query but then I didn't add it because it doesn't evaluate the expression as you noticed. On the other hand, we can assume that the injected code is likely to be run a bit later when the variable is used. I've added this sink. Thanks for the suggestion!
| taintFromExpr = this.getArgument(1) and | ||
| exists(Method m | this.(MethodAccess).getMethod() = m | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The exists should probably be in parentheses?
Otherwise for the other exists below trainFromExpr is not bound to the call argument.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! Thanks!
java/ql/src/experimental/Security/CWE/CWE-094/SaferExpressionEvaluationWithJuel.java
Outdated
Show resolved
Hide resolved
| MethodExpression() { hasName("MethodExpression") } | ||
| } | ||
|
|
||
| private class LambdaExpression extends RefType { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't the base type be JakartaType?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup. Thanks!
Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>
61c4f0b to
b39a3ab
Compare
|
@Marcono1234 @tamasvajk Thanks for the review! I've addressed your comments -- please have a look. |
| The following example shows how untrusted data is used to build and run an expression | ||
| using the JUEL interpreter: | ||
| </p> | ||
| <sample src="UnsafeExpressionEvaluationWithJUEL.java" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please adjust the casing of this file reference?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forgot to rename it here, thanks!
| incoming data has to be checked before including it in an expression. The next example | ||
| uses a Regex pattern to check whether a user tries to run an allowed expression or not: | ||
| </p> | ||
| <sample src="SaferExpressionEvaluationWithJUEL.java" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please adjust the casing of this file reference?
| * Holds if `fromNode` to `toNode` is a dataflow step that returns data from | ||
| * a bean by calling one of its getters. | ||
| */ | ||
| predicate returnsDataFromBean(DataFlow::Node fromNode, DataFlow::Node toNode) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem to have anything to do with beans, it's just any old getter. Rename hasGetterFlow?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, the predicate doesn't fully describe a Java bean but I meant a call to a getter of a bean. No problem, I renamed it to hasGetterFlow.
| @@ -0,0 +1,14 @@ | |||
| import java | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
InjectionLib is an odd name for a file containing only a taint step traversing getters. Given it's query-local perhaps just Utils or FlowUtils?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I renamed it to FlowUtils.
| <sample src="UnsafeExpressionEvaluationWithJUEL.java" /> | ||
|
|
||
| <p> | ||
| JUEL does not support to run expressions in a sandbox. To prevent running arbitrary code, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| JUEL does not support to run expressions in a sandbox. To prevent running arbitrary code, | |
| JUEL does not support running expressions in a sandbox. To prevent running arbitrary code, |
|
@tamasvajk @smowton Thanks for your comments -- I've updated the changeset. |
|
One last thing: could you label your tests with comments like |
@smowton Sure, I've updated the tests. |
Jakarta EE contains a specification for an expression language (EL) and API for interpreters. There are multiple implementations of the API, for example, JUEL, Apache Commons EL, Glassfish has its own implementation. The language allows invocation of methods available in the JVM. If an expression is built using attacker-controlled data, and then evaluated, it may allow the attacker to run arbitrary code in the worst case.
I'd like to propose a new experimental query that looks for potential expression injections when an application uses the Jakarta EL.
Jakarta EE used to be Java EE in the past. Originally, the API for the EL was located in the
javax.elpackage. After Eclipse Foundation had taken over Java EE, the package was renamed tojakarta.el. The query covers both versions.The query detects a known RCE in OpenFaces.