Skip to content

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

Merged
merged 15 commits into from
Mar 20, 2023

Conversation

erik-krogh
Copy link
Contributor

@erik-krogh erik-krogh commented Nov 29, 2022

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.

@github-actions github-actions bot added the Ruby label Nov 29, 2022
@erik-krogh erik-krogh marked this pull request as ready for review February 2, 2023 08:39
@erik-krogh erik-krogh requested a review from a team as a code owner February 2, 2023 08:39
@erik-krogh erik-krogh added the no-change-note-required This PR does not need a change note label Feb 2, 2023
@calumgrant calumgrant requested a review from hvitved February 6, 2023 09:30
Copy link
Contributor

@hvitved hvitved left a 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).

@erik-krogh
Copy link
Contributor Author

Couldn't we instead simply define a taint-summary for join?

Yes, that works for .join() 👍

@@ -1186,6 +1186,16 @@
}
}

private class JoinSummary extends SimpleSummarizedCallable {
JoinSummary() { this = ["join"] }

Check warning

Code scanning / CodeQL

Singleton set literal

Singleton set literal can be replaced by its member.
* 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) {
Copy link
Contributor

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.

Copy link
Contributor Author

@erik-krogh erik-krogh Feb 28, 2023

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.

Copy link
Contributor

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]"

Copy link
Contributor Author

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.

@erik-krogh erik-krogh requested a review from a team as a code owner February 28, 2023 15:10
@erik-krogh erik-krogh requested a review from hvitved February 28, 2023 15:10
Copy link
Contributor

@hvitved hvitved left a 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
Copy link
Contributor

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

Choose a reason for hiding this comment

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

Same

@erik-krogh
Copy link
Contributor Author

Also, we should have a final DCA run.

It blew up.
I'll investigate, but I don't think I'll have time for that this week.
I'm putting this in draft, and will undraft again when I've figured out what's happening.

@erik-krogh erik-krogh marked this pull request as draft March 1, 2023 20:24
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()
Copy link
Contributor

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

@erik-krogh
Copy link
Contributor Author

New evaluations (nightly, rails-projects) show OK performance and some new results.

Triaging the new results they are mostly from new steps through .join("..").
And for most of the results we already flagged the sink from another source, but the extra completeness is nice.

@erik-krogh erik-krogh marked this pull request as ready for review March 16, 2023 12:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
no-change-note-required This PR does not need a change note Ruby
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants