Skip to content

Conversation

@artem-smotrakov
Copy link
Contributor

@artem-smotrakov artem-smotrakov commented Mar 21, 2021

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.el package. After Eclipse Foundation had taken over Java EE, the package was renamed to jakarta.el. The query covers both versions.

The query detects a known RCE in OpenFaces.

Copy link
Contributor

@Marcono1234 Marcono1234 left a 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):

codeql/.lgtm.yml

Lines 5 to 6 in 34e2562

test:

So you could adjust your comment on the OpenFaces GitHub issue.

Comment on lines 61 to 62
Apache Foundation:
<a href="https://commons.apache.org/dormant/commons-el/">Apache Commons EL</a>
Copy link
Contributor

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?

Copy link
Contributor Author

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
Copy link
Contributor

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.)

Copy link
Contributor Author

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!

Copy link
Contributor

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
Copy link
Contributor

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)

Copy link
Contributor Author

@artem-smotrakov artem-smotrakov Apr 8, 2021

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!

Comment on lines 58 to 59
taintFromExpr = this.getArgument(1) and
exists(Method m | this.(MethodAccess).getMethod() = m |
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! Thanks!

MethodExpression() { hasName("MethodExpression") }
}

private class LambdaExpression extends RefType {
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup. Thanks!

@artem-smotrakov
Copy link
Contributor Author

@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" />
Copy link
Contributor

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?

Copy link
Contributor Author

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" />
Copy link
Contributor

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) {
Copy link
Contributor

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?

Copy link
Contributor Author

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
Copy link
Contributor

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?

Copy link
Contributor Author

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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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,

@artem-smotrakov
Copy link
Contributor Author

@tamasvajk @smowton Thanks for your comments -- I've updated the changeset.

@smowton
Copy link
Contributor

smowton commented Apr 13, 2021

One last thing: could you label your tests with comments like // BAD (untrusted input to evalExpression) (when a case should be flagged) or // GOOD (untrusted input validated by xyz) when it shouldn't?

@artem-smotrakov
Copy link
Contributor Author

One last thing: could you label your tests with comments like // BAD (untrusted input to evalExpression) (when a case should be flagged) or // GOOD (untrusted input validated by xyz) when it shouldn't?

@smowton Sure, I've updated the tests.

@smowton smowton dismissed tamasvajk’s stale review April 15, 2021 08:41

Changes applied

@smowton smowton merged commit fa36ba9 into github:main Apr 15, 2021
@artem-smotrakov artem-smotrakov deleted the el-injection branch April 15, 2021 11:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants