Skip to content

Conversation

erik-krogh
Copy link
Contributor

This PR adds the following sanitizer in js/path-injection:

var relative = path.relative(webroot, pathname);
if(relative.startsWith(".." + path.sep) || relative == "..") {
    return res.sendStatus(404); // pathname is bad
} else {
   fs.writeFileSync(pathname, contents); // pathname is sanitized
}

This sanitizer does not work with Windows drive letters (a relative path from C:\foo\bar to D:\baz\bla is just D:\baz\bla). But I still think it is good enough for us to use it as a sanitizer.

The sanitizer is used in the fix for CVE-2018-3787, and adding the sanitizer fixes all the new FPs that arose as part of #2810.

An evaluation look OK performance wise .

@erik-krogh erik-krogh added the JS label Feb 14, 2020
@erik-krogh erik-krogh requested a review from a team as a code owner February 14, 2020 10:58
Copy link
Contributor

@esbena esbena left a comment

Choose a reason for hiding this comment

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

Very nice. I think we can generalise this a bit more.

(The unrelated formatting and feature addition should have gone into a separate commit or PR IMO)

relativeCall = DataFlow::moduleImport("path").getAMemberCall("relative") and
startsWith.getBaseString().getALocalSource() = relativeCall and
exists(DataFlow::Node subString | subString = startsWith.getSubstring() |
subString.mayHaveStringValue("..")
Copy link
Contributor

Choose a reason for hiding this comment

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

Here and below. Lets support the "../" prefixes as well.

}

/**
* A data flow sink for tainted-path vulnerabilities.
*/
abstract class Sink extends DataFlow::Node {
Sink() { not this instanceof Sanitizer }
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems out of place for this PR, and if we want it, then it is perhaps something that should be implemented in Configuration.qll as something like isReallySink(Node sink) { isSink(sink) and not isBarrier(sink) }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was part of my testing for the PR and it should have been removed, but I forgot.

}

let newpath = pathModule.normalize(p);
var relativePath = path.relative(path.normalize(workspaceDir), newpath);
Copy link
Contributor

Choose a reason for hiding this comment

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

In QL, do we want to support the pattern of path.normalize(path.relative(x, newpath));? Programmers may want to be extra safe that they are doing the right thing, so the redundancy is not completely unrealistic IMO.

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 didn't find anyone using that pattern, but it doesn't sound unreasonable, so I added support for it.

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.

Very nice!

This sanitizer does not work with Windows drive letters

That's fine. This query deliberately does not deal with Window-specific path problems. My take is that we should handle Windows-specific issues in a separate query so it can easily be disabled for codebases that don't support Windows anyway.

|
subString.mayHaveStringValue(prefix)
or
subString.(StringOps::ConcatenationRoot).getFirstLeaf().mayHaveStringValue(prefix)
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we use isDotDotSlashPrefix here, like in StartsWithDotDotSanitizer?

@@ -355,6 +351,47 @@ module TaintedPath {
}
}

/**
* A sanitizer that recognizes the following pattern:
* var relative = path.relative(webroot, pathname);
Copy link
Contributor

Choose a reason for hiding this comment

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

Add triple backticks around the code block.


RelativePathContainsDotDotGuard() {
this = startsWith and
relativeCall = DataFlow::moduleImport("path").getAMemberCall("relative") and
Copy link
Contributor

Choose a reason for hiding this comment

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

Use NodeJSLib::Path::moduleMember("relative") instead

}

override predicate blocks(boolean outcome, Expr e) {
e = relativeCall.getArgument(1).asExpr() and outcome = false
Copy link
Contributor

Choose a reason for hiding this comment

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

StartsWith checks have a polarity, so we need to check that here:

outcome = startsWith.getPolarity().booleanNot()

Could you also add a test that uses a negative starts-with check:

if (x.indexOf(y)) { ... } else { sanitized }

* // pathname is safe
* }
*/
class RelativePathContainsDotDotGuard extends DataFlow::BarrierGuardNode {
Copy link
Contributor

Choose a reason for hiding this comment

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

RelativePathContainsDotDotGuard -> RelativePathStartsWithDotDotSanitizer

The other guards are called Sanitizer so we might as well be consistent, and this handles StartsWith checks, not inclusion checks.

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.

Again, sorry for not following up earlier.

LGTM now, but will unfortunately need conflict resolution now.

@asgerf
Copy link
Contributor

asgerf commented Feb 26, 2020

@esbena any more comments? (you're still requesting changes)

@semmle-qlci semmle-qlci merged commit 326522c into github:master Feb 26, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants