Java: Query for detecting JEXL injections #4965
Conversation
- Added TaintedSpringRequestBody source - Added returningTaintedDataFromBean() taint step - Added tests
|
I noticed that parameters annotated with several Spring annotations are not considered as a source of taint. For example: @PostMapping("/request")
public ResponseEntity requestEndpoint(@RequestBody Data data) {Here the
Then, I added a new source I also thought that an application may take a complex object as an endpoint parameter. Currently, the taint is not propagated in this case. I added a new taint step |
|
Because there are a lot of taint-propagating methods here and a lot of duplicated code, how about we abbreviate the whole thing with something like:
This sort of abbreviation won't suit all models, but I suspect it could get 464 lines of code down to 200 or so. |
java/ql/src/experimental/Security/CWE/CWE-094/JexlInjection.qhelp
Outdated
Show resolved
Hide resolved
| class TaintedSpringRequestBody extends DataFlow::Node { | ||
| TaintedSpringRequestBody() { | ||
| exists(SpringServletInputAnnotation a | this.asParameter().getAnAnnotation() = a) | ||
| } | ||
| } |
smowton
Jan 18, 2021
Contributor
I think the various Spring flow sources, which are included in RemoteFlowSource, should remove the need for this class (https://github.com/github/codeql/blob/main/java/ql/src/semmle/code/java/dataflow/FlowSources.qll#L110)
I think the various Spring flow sources, which are included in RemoteFlowSource, should remove the need for this class (https://github.com/github/codeql/blob/main/java/ql/src/semmle/code/java/dataflow/FlowSources.qll#L110)
artem-smotrakov
Jan 23, 2021
•
Author
Contributor
(please also see my previous comment #4965 (comment))
At first, I used only RemoteFlowSource. I know that it contains various Spring flow sources. Then, I wrote several test cases for Spring, please see them in Jexl3Injection.lava:
testWithSpringControllerThatEvaluatesJexlFromPathVariable
testWithSpringControllerThatEvaluatesJexlFromRequestBody
testWithSpringControllerThatEvaluatesJexlFromRequestBodyWithNestedObjects
It turned out that those cases are not detected if only RemoteFlowSource is used. For example:
@PostMapping("/request")
public ResponseEntity testWithSpringControllerThatEvaluatesJexlFromPathVariable(
@PathVariable String expr) {
runJexlExpression(expr);
return ResponseEntity.ok(HttpStatus.OK);
}
The expr string comes from the URL path and therefore should be considered as a flow source. However, RemoteFlowSource doesn't seem to cover such cases. If I remove TaintedSpringRequestBody, then the tests above fail.
Maybe I missed some existing flow sources that cover such cases. If so, I think the at least should be included in RemoteFlowSource. Meanwhile, I'd like to keep TaintedSpringRequestBody. Maybe there is a better place for this class - please let me know. And please let me know if I am missing something.
(please also see my previous comment #4965 (comment))
At first, I used only RemoteFlowSource. I know that it contains various Spring flow sources. Then, I wrote several test cases for Spring, please see them in Jexl3Injection.lava:
testWithSpringControllerThatEvaluatesJexlFromPathVariabletestWithSpringControllerThatEvaluatesJexlFromRequestBodytestWithSpringControllerThatEvaluatesJexlFromRequestBodyWithNestedObjects
It turned out that those cases are not detected if only RemoteFlowSource is used. For example:
@PostMapping("/request")
public ResponseEntity testWithSpringControllerThatEvaluatesJexlFromPathVariable(
@PathVariable String expr) {
runJexlExpression(expr);
return ResponseEntity.ok(HttpStatus.OK);
}The expr string comes from the URL path and therefore should be considered as a flow source. However, RemoteFlowSource doesn't seem to cover such cases. If I remove TaintedSpringRequestBody, then the tests above fail.
Maybe I missed some existing flow sources that cover such cases. If so, I think the at least should be included in RemoteFlowSource. Meanwhile, I'd like to keep TaintedSpringRequestBody. Maybe there is a better place for this class - please let me know. And please let me know if I am missing something.
smowton
Jan 25, 2021
Contributor
That suggests these really are missing, but they don't relate particularly to JEXL -- please open a separate PR to add these.
That suggests these really are missing, but they don't relate particularly to JEXL -- please open a separate PR to add these.
artem-smotrakov
Jan 25, 2021
Author
Contributor
@smowton I agree, they don't relate to JEXL. If I remove TaintedSpringRequestBody now, then some tests will start failing. I'll then need to remove them as well. But I'd prefer to keep them. How about we keep TaintedSpringRequestBody here fore now? Once this pull request is accepted, I can open another one that moves TaintedSpringRequestBody to a better place. What do you think?
@smowton I agree, they don't relate to JEXL. If I remove TaintedSpringRequestBody now, then some tests will start failing. I'll then need to remove them as well. But I'd prefer to keep them. How about we keep TaintedSpringRequestBody here fore now? Once this pull request is accepted, I can open another one that moves TaintedSpringRequestBody to a better place. What do you think?
smowton
Jan 25, 2021
Contributor
Sure, that order is ok
Sure, that order is ok
java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll
Outdated
Show resolved
Hide resolved
java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll
Outdated
Show resolved
Hide resolved
java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll
Outdated
Show resolved
Hide resolved
java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll
Outdated
Show resolved
Hide resolved
java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll
Outdated
Show resolved
Hide resolved
| */ | ||
| predicate returningTaintedDataFromBean(DataFlow::Node node1, DataFlow::Node node2) { | ||
| exists(MethodAccess ma, Method m | ma.getMethod() = m | | ||
| m instanceof GetterMethod and |
smowton
Jan 18, 2021
Contributor
I note we already have m instanceof GetterMethod and m.getDeclaringType() instanceof SpringUntrustedDataType in taintPreservingQualifierToMethod -- does that include the cases you need? Check if any of your tests fail without this?
I note we already have m instanceof GetterMethod and m.getDeclaringType() instanceof SpringUntrustedDataType in taintPreservingQualifierToMethod -- does that include the cases you need? Check if any of your tests fail without this?
artem-smotrakov
Jan 23, 2021
•
Author
Contributor
Yes, unfortunately, one of the tests fails without this (please see Jexl3Injection.java):
testWithSpringControllerThatEvaluatesJexlFromRequestBodyWithNestedObjects
This test covers the following case:
- Tainted data comes from a complex bean
CustomRequest that has @RequestBody annotation.
- The
CustomRequest class contains another bean of type Data.
@PostMapping("/request")
public ResponseEntity testWithSpringControllerThatEvaluatesJexlFromRequestBodyWithNestedObjects(
@RequestBody CustomRequest customRequest) {
String expr = customRequest.getData().getExpr();
runJexlExpression(expr);
The TaintedSpringRequestBody flow source and returningTaintedDataFromBean step work together to catch this case.
I know that this step may be too broad so that it causes many false-positives (please see #4965 (comment)). I've been testing this on several codebases, so far it discovered some true-positives. I still need some time to finish the analysis. Taking into account that the query is experimental, I'd like to keep this taint propagation step. The impact of a JEXL injection is arbitrary code execution. For such a high impact, maybe it's okay to sacrifice the false-positive rate a bit in order to find more true-positives.
Yes, unfortunately, one of the tests fails without this (please see Jexl3Injection.java):
testWithSpringControllerThatEvaluatesJexlFromRequestBodyWithNestedObjects
This test covers the following case:
- Tainted data comes from a complex bean
CustomRequestthat has@RequestBodyannotation. - The
CustomRequestclass contains another bean of typeData.
@PostMapping("/request")
public ResponseEntity testWithSpringControllerThatEvaluatesJexlFromRequestBodyWithNestedObjects(
@RequestBody CustomRequest customRequest) {
String expr = customRequest.getData().getExpr();
runJexlExpression(expr);The TaintedSpringRequestBody flow source and returningTaintedDataFromBean step work together to catch this case.
I know that this step may be too broad so that it causes many false-positives (please see #4965 (comment)). I've been testing this on several codebases, so far it discovered some true-positives. I still need some time to finish the analysis. Taking into account that the query is experimental, I'd like to keep this taint propagation step. The impact of a JEXL injection is arbitrary code execution. For such a high impact, maybe it's okay to sacrifice the false-positive rate a bit in order to find more true-positives.
artem-smotrakov
Jan 23, 2021
Author
Contributor
TaintedSpringRequestBody and returningTaintedDataFromBean allowed to detect traccar/traccar#4624
TaintedSpringRequestBody and returningTaintedDataFromBean allowed to detect traccar/traccar#4624
smowton
Jan 25, 2021
Contributor
Do you think the code to retrieve taint from a complex bean like that could be made universal? If so let's make a separate PR for that too -- if on the other hand you think it would be too noisy and should be restricted to this JEXL query then let's keep it as you say.
Do you think the code to retrieve taint from a complex bean like that could be made universal? If so let's make a separate PR for that too -- if on the other hand you think it would be too noisy and should be restricted to this JEXL query then let's keep it as you say.
artem-smotrakov
Jan 25, 2021
Author
Contributor
I think it would be too noisy if we make it universal (at least with the current version of the step). Let's please keep it restricted to this JEXL query.
I think it would be too noisy if we make it universal (at least with the current version of the step). Let's please keep it restricted to this JEXL query.
java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll
Outdated
Show resolved
Hide resolved
java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll
Outdated
Show resolved
Hide resolved
@smowton Thanks for the suggestion! I am still learning CodeQL and looking for ways to make the code shorter. I'll try to apply the suggestion. |
|
Looks like some comments are applied and others not at the moment -- please ping me here when you're ready for another review |
Yeah, I didn't address some of them yet. Need some time. I'll let you know. |
java/ql/src/experimental/Security/CWE/CWE-094/JexlInjection.qhelp
Outdated
Show resolved
Hide resolved
java/ql/src/experimental/Security/CWE/CWE-094/JexlInjection.qhelp
Outdated
Show resolved
Hide resolved
java/ql/src/experimental/Security/CWE/CWE-094/JexlInjection.qhelp
Outdated
Show resolved
Hide resolved
|
Thanks for the suggestion @intrigus-lgtm ! I forgot about |
Also happens to me every now and then :) |
|
@smowton Thanks for the review and suggestions! I've addressed your comments. I've also tried to simplify the code by applying your hints to the rest of the code. I would however still prefer using method/class definitions. I also make them a bit shorter. Please also see my comments about |
|
Mostly looking good now, just one more major abbreviation we can make |
| * It supports both Jexl2 and Jexl3. | ||
| */ | ||
| class JexlInjectionConfig extends TaintTracking::Configuration { | ||
| TaintPropagatingJexlMethodCall taintPropagatingJexlMethodCall; |
smowton
Jan 25, 2021
Contributor
Suggested change
TaintPropagatingJexlMethodCall taintPropagatingJexlMethodCall;
Using this as a field generates multiple instances of the Configuration. I'm not sure the consequences of that, but they surely can't be good :)
| TaintPropagatingJexlMethodCall taintPropagatingJexlMethodCall; |
Using this as a field generates multiple instances of the Configuration. I'm not sure the consequences of that, but they surely can't be good :)
artem-smotrakov
Jan 25, 2021
Author
Contributor
I didn't know about it, thanks! I'll apply this suggestion.
I didn't know about it, thanks! I'll apply this suggestion.
| override predicate isSink(DataFlow::Node sink) { sink instanceof JexlEvaluationSink } | ||
|
|
||
| override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) { | ||
| taintPropagatingJexlMethodCall.taintFlow(fromNode, toNode) or |
smowton
Jan 25, 2021
Contributor
Suggested change
taintPropagatingJexlMethodCall.taintFlow(fromNode, toNode) or
any(TaintPropagatingJexlMethodCall c).taintFlow(fromNode, toNode) or
| taintPropagatingJexlMethodCall.taintFlow(fromNode, toNode) or | |
| any(TaintPropagatingJexlMethodCall c).taintFlow(fromNode, toNode) or |
| */ | ||
| private class JexlEvaluationSink extends DataFlow::ExprNode { | ||
| JexlEvaluationSink() { | ||
| exists(MethodAccess ma, Method m, Expr tainted | ma.getMethod() = m and tainted = asExpr() | |
smowton
Jan 25, 2021
•
Contributor
Suggested change
exists(MethodAccess ma, Method m, Expr tainted | ma.getMethod() = m and tainted = asExpr() |
exists(MethodAccess ma, Method m, Expr taintFrom | ma.getMethod() = m and tainted = this.asExpr() |
(fairly arbitrary style preference in this repo: use explicit this)
Rename var to be slightly clearer about its role
| exists(MethodAccess ma, Method m, Expr tainted | ma.getMethod() = m and tainted = asExpr() | | |
| exists(MethodAccess ma, Method m, Expr taintFrom | ma.getMethod() = m and tainted = this.asExpr() | |
(fairly arbitrary style preference in this repo: use explicit this)
Rename var to be slightly clearer about its role
| /** | ||
| * Defines methods that triggers direct evaluation of Jexl expressions. | ||
| */ | ||
| abstract private class DirectJexlEvaluationMethod extends Method { } | ||
|
|
||
| /** | ||
| * A method in the `JexlExpression` class that evaluates a Jexl expression. | ||
| */ | ||
| private class JexlExpressionEvaluateMethod extends DirectJexlEvaluationMethod { | ||
| JexlExpressionEvaluateMethod() { | ||
| getDeclaringType() instanceof JexlExpression and hasName("evaluate") | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * A method in the `JexlScript` class that executes a Jexl script. | ||
| */ | ||
| private class JexlScriptExecuteMethod extends DirectJexlEvaluationMethod { | ||
| JexlScriptExecuteMethod() { getDeclaringType() instanceof JexlScript and hasName("execute") } | ||
| } | ||
|
|
||
| /** | ||
| * A method in the `JxltEngine.Expression` class that evaluates an expression. | ||
| */ | ||
| private class JxltEngineExpressionEvaluateMethod extends DirectJexlEvaluationMethod { | ||
| JxltEngineExpressionEvaluateMethod() { | ||
| getDeclaringType() instanceof JxltEngineExpression and hasName("evaluate") | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * A method in the `JxltEngine.Expression` class that evaluates the immediate sub-expressions. | ||
| */ | ||
| private class JxltEngineExpressionPrepareMethod extends DirectJexlEvaluationMethod { | ||
| JxltEngineExpressionPrepareMethod() { | ||
| getDeclaringType() instanceof JxltEngineExpression and hasName("prepare") | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * A method in the `JxltEngine.Template` class that evaluates a template. | ||
| */ | ||
| private class JxltEngineTemplateEvaluateMethod extends DirectJexlEvaluationMethod { | ||
| JxltEngineTemplateEvaluateMethod() { | ||
| getDeclaringType() instanceof JxltEngineTemplate and hasName("evaluate") | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * A method in the `UnifiedJEXL.Expression` class that evaluates a template. | ||
| */ | ||
| private class UnifiedJexlExpressionEvaluateMethod extends DirectJexlEvaluationMethod { | ||
| UnifiedJexlExpressionEvaluateMethod() { | ||
| getDeclaringType() instanceof UnifiedJexlExpression and hasName("evaluate") | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * A method in the `UnifiedJEXL.Expression` class that evaluates the immediate sub-expressions. | ||
| */ | ||
| private class UnifiedJexlExpressionPrepareMethod extends DirectJexlEvaluationMethod { | ||
| UnifiedJexlExpressionPrepareMethod() { | ||
| getDeclaringType() instanceof UnifiedJexlExpression and hasName("prepare") | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * A method in the `UnifiedJEXL.Template` class that evaluates a template. | ||
| */ | ||
| private class UnifiedJexlTemplateEvaluateMethod extends DirectJexlEvaluationMethod { | ||
| UnifiedJexlTemplateEvaluateMethod() { | ||
| getDeclaringType() instanceof UnifiedJexlTemplate and hasName("evaluate") | ||
| } | ||
| } |
smowton
Jan 25, 2021
Contributor
Suggested change
/**
* Defines methods that triggers direct evaluation of Jexl expressions.
*/
abstract private class DirectJexlEvaluationMethod extends Method { }
/**
* A method in the `JexlExpression` class that evaluates a Jexl expression.
*/
private class JexlExpressionEvaluateMethod extends DirectJexlEvaluationMethod {
JexlExpressionEvaluateMethod() {
getDeclaringType() instanceof JexlExpression and hasName("evaluate")
}
}
/**
* A method in the `JexlScript` class that executes a Jexl script.
*/
private class JexlScriptExecuteMethod extends DirectJexlEvaluationMethod {
JexlScriptExecuteMethod() { getDeclaringType() instanceof JexlScript and hasName("execute") }
}
/**
* A method in the `JxltEngine.Expression` class that evaluates an expression.
*/
private class JxltEngineExpressionEvaluateMethod extends DirectJexlEvaluationMethod {
JxltEngineExpressionEvaluateMethod() {
getDeclaringType() instanceof JxltEngineExpression and hasName("evaluate")
}
}
/**
* A method in the `JxltEngine.Expression` class that evaluates the immediate sub-expressions.
*/
private class JxltEngineExpressionPrepareMethod extends DirectJexlEvaluationMethod {
JxltEngineExpressionPrepareMethod() {
getDeclaringType() instanceof JxltEngineExpression and hasName("prepare")
}
}
/**
* A method in the `JxltEngine.Template` class that evaluates a template.
*/
private class JxltEngineTemplateEvaluateMethod extends DirectJexlEvaluationMethod {
JxltEngineTemplateEvaluateMethod() {
getDeclaringType() instanceof JxltEngineTemplate and hasName("evaluate")
}
}
/**
* A method in the `UnifiedJEXL.Expression` class that evaluates a template.
*/
private class UnifiedJexlExpressionEvaluateMethod extends DirectJexlEvaluationMethod {
UnifiedJexlExpressionEvaluateMethod() {
getDeclaringType() instanceof UnifiedJexlExpression and hasName("evaluate")
}
}
/**
* A method in the `UnifiedJEXL.Expression` class that evaluates the immediate sub-expressions.
*/
private class UnifiedJexlExpressionPrepareMethod extends DirectJexlEvaluationMethod {
UnifiedJexlExpressionPrepareMethod() {
getDeclaringType() instanceof UnifiedJexlExpression and hasName("prepare")
}
}
/**
* A method in the `UnifiedJEXL.Template` class that evaluates a template.
*/
private class UnifiedJexlTemplateEvaluateMethod extends DirectJexlEvaluationMethod {
UnifiedJexlTemplateEvaluateMethod() {
getDeclaringType() instanceof UnifiedJexlTemplate and hasName("evaluate")
}
}
/**
* Defines methods that triggers direct evaluation of Jexl expressions.
*/
private class DirectJexlEvaluationMethod extends Method {
DirectJexlEvaluationMethod() {
getDeclaringType() instanceof JexlExpression and hasName("evaluate") or
getDeclaringType() instanceof JexlScript and hasName("execute") or
getDeclaringType() instanceof JxltEngineExpression and hasName(["evaluate", "prepare"]) or
getDeclaringType() instanceof JxltEngineTemplate and hasName("evaluate") or
getDeclaringType() instanceof UnifiedJexlExpression and hasName(["evaluate", "prepare"]) or
getDeclaringType() instanceof UnifiedJexlTemplate and hasName("evaluate")
}
}
Haven't checked, but if the other types lack a prepare method altogether then this could becomes just a list of 6 types and hasName(["evaluate", "prepare"])
| /** | |
| * Defines methods that triggers direct evaluation of Jexl expressions. | |
| */ | |
| abstract private class DirectJexlEvaluationMethod extends Method { } | |
| /** | |
| * A method in the `JexlExpression` class that evaluates a Jexl expression. | |
| */ | |
| private class JexlExpressionEvaluateMethod extends DirectJexlEvaluationMethod { | |
| JexlExpressionEvaluateMethod() { | |
| getDeclaringType() instanceof JexlExpression and hasName("evaluate") | |
| } | |
| } | |
| /** | |
| * A method in the `JexlScript` class that executes a Jexl script. | |
| */ | |
| private class JexlScriptExecuteMethod extends DirectJexlEvaluationMethod { | |
| JexlScriptExecuteMethod() { getDeclaringType() instanceof JexlScript and hasName("execute") } | |
| } | |
| /** | |
| * A method in the `JxltEngine.Expression` class that evaluates an expression. | |
| */ | |
| private class JxltEngineExpressionEvaluateMethod extends DirectJexlEvaluationMethod { | |
| JxltEngineExpressionEvaluateMethod() { | |
| getDeclaringType() instanceof JxltEngineExpression and hasName("evaluate") | |
| } | |
| } | |
| /** | |
| * A method in the `JxltEngine.Expression` class that evaluates the immediate sub-expressions. | |
| */ | |
| private class JxltEngineExpressionPrepareMethod extends DirectJexlEvaluationMethod { | |
| JxltEngineExpressionPrepareMethod() { | |
| getDeclaringType() instanceof JxltEngineExpression and hasName("prepare") | |
| } | |
| } | |
| /** | |
| * A method in the `JxltEngine.Template` class that evaluates a template. | |
| */ | |
| private class JxltEngineTemplateEvaluateMethod extends DirectJexlEvaluationMethod { | |
| JxltEngineTemplateEvaluateMethod() { | |
| getDeclaringType() instanceof JxltEngineTemplate and hasName("evaluate") | |
| } | |
| } | |
| /** | |
| * A method in the `UnifiedJEXL.Expression` class that evaluates a template. | |
| */ | |
| private class UnifiedJexlExpressionEvaluateMethod extends DirectJexlEvaluationMethod { | |
| UnifiedJexlExpressionEvaluateMethod() { | |
| getDeclaringType() instanceof UnifiedJexlExpression and hasName("evaluate") | |
| } | |
| } | |
| /** | |
| * A method in the `UnifiedJEXL.Expression` class that evaluates the immediate sub-expressions. | |
| */ | |
| private class UnifiedJexlExpressionPrepareMethod extends DirectJexlEvaluationMethod { | |
| UnifiedJexlExpressionPrepareMethod() { | |
| getDeclaringType() instanceof UnifiedJexlExpression and hasName("prepare") | |
| } | |
| } | |
| /** | |
| * A method in the `UnifiedJEXL.Template` class that evaluates a template. | |
| */ | |
| private class UnifiedJexlTemplateEvaluateMethod extends DirectJexlEvaluationMethod { | |
| UnifiedJexlTemplateEvaluateMethod() { | |
| getDeclaringType() instanceof UnifiedJexlTemplate and hasName("evaluate") | |
| } | |
| } | |
| /** | |
| * Defines methods that triggers direct evaluation of Jexl expressions. | |
| */ | |
| private class DirectJexlEvaluationMethod extends Method { | |
| DirectJexlEvaluationMethod() { | |
| getDeclaringType() instanceof JexlExpression and hasName("evaluate") or | |
| getDeclaringType() instanceof JexlScript and hasName("execute") or | |
| getDeclaringType() instanceof JxltEngineExpression and hasName(["evaluate", "prepare"]) or | |
| getDeclaringType() instanceof JxltEngineTemplate and hasName("evaluate") or | |
| getDeclaringType() instanceof UnifiedJexlExpression and hasName(["evaluate", "prepare"]) or | |
| getDeclaringType() instanceof UnifiedJexlTemplate and hasName("evaluate") | |
| } | |
| } |
Haven't checked, but if the other types lack a prepare method altogether then this could becomes just a list of 6 types and hasName(["evaluate", "prepare"])
artem-smotrakov
Jan 25, 2021
Author
Contributor
Thanks for the suggestion - that makes it much shorter! Technically, I can write the following even if the types don't have all three methods:
private class DirectJexlEvaluationMethod extends Method {
DirectJexlEvaluationMethod() {
(
getDeclaringType() instanceof JexlExpression
or
getDeclaringType() instanceof JexlScript
or
getDeclaringType() instanceof JxltEngineExpression
or
getDeclaringType() instanceof JxltEngineTemplate
or
getDeclaringType() instanceof UnifiedJexlExpression
or
getDeclaringType() instanceof UnifiedJexlTemplate
) and
hasName(["evaluate", "execute", "prepare"])
}
}
That would work. However, that looks a bit confusing to me since none of the listed types has all three methods. I'd prefer to use your version that describes the methods of the specific classes. Let me know if I am missing something.
Thanks for the suggestion - that makes it much shorter! Technically, I can write the following even if the types don't have all three methods:
private class DirectJexlEvaluationMethod extends Method {
DirectJexlEvaluationMethod() {
(
getDeclaringType() instanceof JexlExpression
or
getDeclaringType() instanceof JexlScript
or
getDeclaringType() instanceof JxltEngineExpression
or
getDeclaringType() instanceof JxltEngineTemplate
or
getDeclaringType() instanceof UnifiedJexlExpression
or
getDeclaringType() instanceof UnifiedJexlTemplate
) and
hasName(["evaluate", "execute", "prepare"])
}
}
That would work. However, that looks a bit confusing to me since none of the listed types has all three methods. I'd prefer to use your version that describes the methods of the specific classes. Let me know if I am missing something.
- Merged multiple method definitions to DirectJexlEvaluationMethod - Don't use TaintPropagatingJexlMethodCall field in JexlInjectionConfig - Better variable names in JexlEvaluationSink
|
@smowton I've applied your suggestions and formatted the code. Please have a look. |
|
Great, this is looking good! Are you applying to the bounty program with this PR? |
Thanks for the review! Yes, I am planning to apply to the bounty program once this PR is merged. |
|
You should actually make your application now -- then the security lab folks will help evaluate the quality of the results and perhaps make more suggestions here. |
|
Sure, created github/securitylab#249 |
| ma.getAnArgument().getType() instanceof TypeString and | ||
| ma.getAnArgument() = taintFrom |
smowton
Feb 9, 2021
Contributor
Doh, yes, you could check taintFrom.getType() to make sure you're referring to the same arg
Doh, yes, you could check taintFrom.getType() to make sure you're referring to the same arg
artem-smotrakov
Feb 10, 2021
Author
Contributor
I've updated the query, thanks!
I've updated the query, thanks!
| | | ||
| m instanceof DirectJexlEvaluationMethod and ma.getQualifier() = taintFrom | ||
| or | ||
| m instanceof CallableCallMethod and ma.getQualifier() = taintFrom |
pwntester
Feb 9, 2021
Contributor
This is matching all calls to java.util.concurrent.Callable no matter if they are created from a JEXL script or not which is adding quite a few false positives
This is matching all calls to java.util.concurrent.Callable no matter if they are created from a JEXL script or not which is adding quite a few false positives
artem-smotrakov
Feb 10, 2021
Author
Contributor
To produce an alert, a Callable has to be tainted. This sink works together with TaintPropagatingJexlMethodCall that can make a Callable tainted if it is created from a tainted JexlExpression or JexlScript. There is no other way how a Callable can become tainted in this query. The sink is private, therefore it can't affect other queries. While running the query on multiple codebases, I didn't notice such a false positive. Please let me know if I am missing something. If you have a code snippet that results to a false positive, please let me know. I'll try to fix the query and add it as a test.
To produce an alert, a Callable has to be tainted. This sink works together with TaintPropagatingJexlMethodCall that can make a Callable tainted if it is created from a tainted JexlExpression or JexlScript. There is no other way how a Callable can become tainted in this query. The sink is private, therefore it can't affect other queries. While running the query on multiple codebases, I didn't notice such a false positive. Please let me know if I am missing something. If you have a code snippet that results to a false positive, please let me know. I'll try to fix the query and add it as a test.
pwntester
Feb 11, 2021
Contributor
Sure, you can use apache/groovy as test case. The project does not use JEXL and still gets 38 alerts. The problem arises when a Callable gets tainted. For example below obj is tainted and gets casted to groovy's Closure which implements the Callable interface:
Closure c = (Closure) ((Object[]) obj)[0];
..
c.call((Object)null);
It may be enough to set the sink in the callable() qualifier assuming that it will get executed at some point, maybe stored in an object field and then reached by a different flow.
Sure, you can use apache/groovy as test case. The project does not use JEXL and still gets 38 alerts. The problem arises when a Callable gets tainted. For example below obj is tainted and gets casted to groovy's Closure which implements the Callable interface:
Closure c = (Closure) ((Object[]) obj)[0];
..
c.call((Object)null);
It may be enough to set the sink in the callable() qualifier assuming that it will get executed at some point, maybe stored in an object field and then reached by a different flow.
artem-smotrakov
Feb 11, 2021
Author
Contributor
You're right, good catch!
It may be enough to set the sink in the callable() qualifier assuming that it will get executed at some point, maybe stored in an object field and then reached by a different flow.
I've updated the query with your suggestion. Now it doesn't show false positives for Apache Groovy. Thanks!
You're right, good catch!
It may be enough to set the sink in the callable() qualifier assuming that it will get executed at some point, maybe stored in an object field and then reached by a different flow.
I've updated the query with your suggestion. Now it doesn't show false positives for Apache Groovy. Thanks!
- Added a dataflow config to track setting a sandbox on JexlBuilder - Added SandboxedJexl3.java test
|
@pwntester As we agreed in github/securitylab#249 (comment), I've updated the query to take into account setting a sandbox. The query now tracks if a sandbox is set with |
- Updated SandboxedJexlFlowConfig to cover JEXL 2 - Added SandboxedJexl2 test
|
I just realized that I forgot to include sandboxes for JEXL 2. Now it is covered. Added a test for that. |
|
Thanks, will request a new run from the CodeQL team and get back to you as soon as possible |
| override predicate isSource(DataFlow::Node node) { node instanceof SandboxedJexlSource } | ||
|
|
||
| override predicate isSink(DataFlow::Node node) { | ||
| node.asExpr().getType() instanceof JexlEngine or |
smowton
Feb 15, 2021
Contributor
This could produce a pretty large set of sinks. Looks like we're only interested in qualifiers to CreateJexlScriptMethod calls and similar -- suggest restricting this to only consider the arguments we will consider in TaintPropagatingJexlMethodCall's characteristic predicate.
This could produce a pretty large set of sinks. Looks like we're only interested in qualifiers to CreateJexlScriptMethod calls and similar -- suggest restricting this to only consider the arguments we will consider in TaintPropagatingJexlMethodCall's characteristic predicate.
|
|
||
| /** | ||
| * Holds if `fromNode` to `toNode` is a dataflow step that returns data from | ||
| * a tainted bean by calling one of its getters. |
smowton
Feb 15, 2021
Contributor
Suggested change
* a tainted bean by calling one of its getters.
* a bean by calling one of its getters.
| * a tainted bean by calling one of its getters. | |
| * a bean by calling one of its getters. |
| } | ||
|
|
||
| /** | ||
| * Method in the `JexlEngine` class that get or set a property with a Jexl expression. |
smowton
Feb 15, 2021
Contributor
Suggested change
* Method in the `JexlEngine` class that get or set a property with a Jexl expression.
* A method in the `JexlEngine` class that get or set a property with a Jexl expression.
| * Method in the `JexlEngine` class that get or set a property with a Jexl expression. | |
| * A method in the `JexlEngine` class that get or set a property with a Jexl expression. |
| } | ||
|
|
||
| /** | ||
| * Defines methods that create a Jexl script. |
smowton
Feb 15, 2021
Contributor
Suggested change
* Defines methods that create a Jexl script.
* A methods that create a Jexl script.
And similarly other uses of Defines ...
| * Defines methods that create a Jexl script. | |
| * A methods that create a Jexl script. |
And similarly other uses of Defines ...
Java Expression Language (JEXL) is a simple expression language provided by the Apache Commons JEXL library. If a JEXL expression is built using attacker-controlled data,
and then evaluated, then it may allow the attacker to run arbitrary code (CWE-094). Here are several examples of JEXL injections:
I'd like to propose a new experimental query that looks for potential JEXL injections:
Here are examples of a true-positives: