-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Rb: more taint-steps for shell-command-construction #11478
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
6e6a327
to
c2e8206
Compare
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.
Couldn't we instead simply define a taint-summary for join
?
input = "Argument[self].Element[any]" and
output = "ReturnValue" and
preservesValue = false
Such a summary should also work when we consider an array it self tainted (as opposed to an element of that array).
Yes, that works for |
* Holds if there an array element `pred` might taint the array defined by `succ`. | ||
* This is used for queries where we consider an entire array to be tainted if any of its elements are tainted. | ||
*/ | ||
predicate taintedArrayObjectSteps(DataFlow::Node pred, DataFlow::Node succ) { |
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 think the better way to do this is to allow implicit array reads at sinks. We could either do this for the two relevant queries, using Configuration::allowImplicitRead
, or for all taint tracking configurations, using defaultImplicitTaintRead
, like e.g. in 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.
That does give me some of the desired behavior, but not all.
This test case is flagged with my "taint-step"-approach, but not with your "implicit-read"-approach.
def join_indirect(x, y)
arr = Array("foo = ", x)
eval(arr.join(" ")) # NOT OK
arr2 = [Array("foo = ", y).join(" ")]
eval(arr2.join("\n")) # NOT OK
end
I'll do the allowImplicitRead
approach for now as it gives me most of the desired behavior (I'm just adding it on the two relevant queries), and then I'll return to the CVE later.
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.
Isn't this because the kernel method Array
does not work like you intend in your examples? But I also don't think we currently have flow summaries for that method; I guess it should be something like
// already an array
input = "Argument[0].WithElement[0..]" and
output = "ReturnValue"
or
// not already an array
input = "Argument[0].WithoutElement[0..]" and
output = "ReturnValue.Element[0]"
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.
You're completely right. I was looking at how it was used in the CVE, and probably just assumed that it was similar to the JS Array()
call.
I've added a summary for Array()
based on your suggestion.
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.
Thanks for addressing my comments. This looks good now, except for two redundant imports.
Also, we should have a final DCA run.
@@ -10,6 +10,7 @@ import codeql.ruby.DataFlow | |||
import UnsafeCodeConstructionCustomizations::UnsafeCodeConstruction | |||
private import codeql.ruby.TaintTracking | |||
private import codeql.ruby.dataflow.BarrierGuards | |||
private import codeql.ruby.frameworks.core.Array |
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.
Can be removed now.
@@ -11,6 +11,7 @@ import UnsafeShellCommandConstructionCustomizations::UnsafeShellCommandConstruct | |||
private import codeql.ruby.TaintTracking | |||
private import CommandInjectionCustomizations::CommandInjection as CommandInjection | |||
private import codeql.ruby.dataflow.BarrierGuards | |||
private import codeql.ruby.frameworks.core.Array |
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.
Same
It blew up. |
result.getMethodName() = "Array" and | ||
// I have to have a simplified "KernelMethodCall" implementation inlined here, because relying on `UnknownMethodCall` results in non-monotonic recursion (even if using `getACall`). | ||
( | ||
result = API::getTopLevelMember("Kernel").getAMethodCall(_).asExpr().getExpr() |
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.
Consider using something like getAStaticArrayCall
here instead, in order to be able to go back to getASimpleCall
(which means that the summary will also be used during type tracking).
New evaluations (nightly, rails-projects) show OK performance and some new results. Triaging the new results they are mostly from new steps through |
Adds some taint steps for CVE-2022-33127
Evaluations (rails-projects, nightly) seems OK, there are a few new results that look acceptable to me.
The rails result is adding an extra source to a sink that was already flagged.
The database result looks good. It's possible to configure which executeable is used, and that is interpreted as a shell-string.