Skip to content

JS: add a "../" removing taint-step for js/path-injection #3197

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 3 commits into from
Apr 3, 2020

Conversation

erik-krogh
Copy link
Contributor

Gets us a TN for the fix in CVE-2017-16107

@erik-krogh erik-krogh added JS Awaiting evaluation Do not merge yet, this PR is waiting for an evaluation to finish labels Apr 3, 2020
@erik-krogh erik-krogh requested a review from a team as a code owner April 3, 2020 07:43
Copy link
Contributor

@asgerf asgerf left a comment

Choose a reason for hiding this comment

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

I like it, but I think we can squeeze a bit more precision out of it. 👍

or
exists(RegExpSequence seq | seq.getNumChild() = 2 and literal.getRoot() = seq |
seq.getChild(0) instanceof RegExpCaret and
seq.getChild(1) = term
Copy link
Contributor

Choose a reason for hiding this comment

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

The anchor should be mandatory, as otherwise ./.././ becomes ../ after the replace.

We've often discussed a separate query to flag this ("multi-character sanitization"), but I think it's worth it for TaintedPath to propagate through such bad sanitizers.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The replace call does not work on ./.././ in the first place (anchor or not), it only works on normalized paths.

And e.g. path.normalize("./.././../foo") === "../../foo".

So I don't see why the anchor should be mandatory.

Copy link
Contributor

Choose a reason for hiding this comment

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

Good point 👍

src = call.getInput() and
dst = call.getOutput() and
dstlabel.isAbsolute() and // result can be absolute
dstlabel.toAbsolute() = srclabel.toAbsolute() // preserves normalization status
Copy link
Contributor

Choose a reason for hiding this comment

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

The result can also be relative.

@erik-krogh
Copy link
Contributor Author

A smoke test evaluation looks fine.

@@ -210,7 +210,7 @@ module TaintedPath {
exists(DotDotSlashPrefixRemovingReplace call |
src = call.getInput() and
dst = call.getOutput() and
dstlabel.isAbsolute() and // result can be absolute
(srclabel.isNonNormalized() or dstlabel.isAbsolute()) and // if src is normalized, then dst must be absolute (if dst is relative, then dst is sanitized)
Copy link
Contributor

Choose a reason for hiding this comment

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

Ok now it's making my head explode 😕

Let's just go over the whole table:

  • Normalized relative -> empty set (completely sanitized)
  • Raw relative ->
    • Raw relative. Example: foo/../bar/../.. becomes foo/bar/../..
    • Raw absolute. Example: ..//foo/bar/../.. becomes /foo/bar/../..
    • Normalized absolute. Example: ..//foo/bar becomes /foo/bar
    • Normalized relative. Example: ..././foo/bar becomes ../foo/bar
  • Normalized absolute ->
    • Normalized absolute (can't contain ../ so unaffected)
  • Raw absolute ->
    • Raw absolute. Example: /foo/../bar/../.. becomes /foo/bar/../..
    • Normalized absolute. Example: /foo/../bar/ becomes /foo/bar.

The line below here doesn't preserve raw->normalized transitions. Missing the raw relative/absolute -> normalized absolute in particular can cause FNs for the FsPathSinkWithoutUpwardNavigation sink.

Perhaps I'm being a stickler but I'd like it if we can get this transition table exactly right.

Copy link
Contributor Author

@erik-krogh erik-krogh Apr 3, 2020

Choose a reason for hiding this comment

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

Ok now it's making my head explode confused

I also had some trouble conceptualizing what was supposed to happen in each case, and how to express this as compact QL. (I considered basically writing out the entire truth table in QL).

Perhaps I'm being a stickler but I'd like it if we can get this transition table exactly right.

It's fine. Thanks for writing out the table.
I ended up writing out the table in the QL code too.

Copy link
Contributor

@asgerf asgerf left a comment

Choose a reason for hiding this comment

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

Thanks! LGTM

@semmle-qlci semmle-qlci merged commit a8098a2 into github:master Apr 3, 2020
output = this and
exists(RegExpLiteral literal, RegExpTerm term |
getArgument(0).getALocalSource().asExpr() = literal and
(term instanceof RegExpStar or term instanceof RegExpPlus) and
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor comment post-merge: this motivates a reuse of SuperlinearBackTracking::InfiniteRepetitionQuantifier, but it probably won' matter in this case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This case is not quite the same as InfiniteRepetitionQuantifier.
Here we need the lower-bound to be zero or one occurrences, and InfiniteRepetitionQuantifier doesn't care about the lower-bound.
(And would e.g. flag /(?:\.\·\/){2,}/).

So I think we can skip such a reuse for now.

exists(RegExpSequence seq | seq = result |
seq.getChild(0).getConstantValue() = "." and
seq.getChild(1).getConstantValue() = "." and
seq.getAChild().getAMatchedString() = "/"
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor comment post-merge: should this have been seq.getAChild(2)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting evaluation Do not merge yet, this PR is waiting for an evaluation to finish JS
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants