Skip to content

Commit 33374ee

Browse files
authored
Merge pull request #2202 from asger-semmle/express-sendfile
Approved by esbena
2 parents b333c6a + 4e3f6c5 commit 33374ee

File tree

9 files changed

+537
-467
lines changed

9 files changed

+537
-467
lines changed

change-notes/1.23/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
| Reflected cross-site scripting (`js/reflected-xss`) | Fewer false-positive results | The query now recognizes more sanitizers. |
4444
| Stored cross-site scripting (`js/stored-xss`) | Fewer false-positive results | The query now recognizes more sanitizers. |
4545
| Uncontrolled command line (`js/command-line-injection`) | More results | This query now treats responses from servers as untrusted. |
46+
| Uncontrolled data used in path expression (`js/path-injection`) | Fewer false-positive results | This query now recognizes calls to Express `sendFile` as safe in some cases. |
4647

4748
## Changes to QL libraries
4849

javascript/ql/src/semmle/javascript/Concepts.qll

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ abstract class FileSystemAccess extends DataFlow::Node {
3939
* sanitization to prevent the path arguments from traversing outside the root folder.
4040
*/
4141
DataFlow::Node getRootPathArgument() { none() }
42+
43+
/**
44+
* Holds if this file system access will reject paths containing upward navigation
45+
* segments (`../`).
46+
*
47+
* `argument` should refer to the relevant path argument or root path argument.
48+
*/
49+
predicate isUpwardNavigationRejected(DataFlow::Node argument) { none() }
4250
}
4351

4452
/**

javascript/ql/src/semmle/javascript/frameworks/Express.qll

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,10 @@ module Express {
839839
override DataFlow::Node getRootPathArgument() {
840840
result = this.(DataFlow::CallNode).getOptionArgument(1, "root")
841841
}
842+
843+
override predicate isUpwardNavigationRejected(DataFlow::Node argument) {
844+
argument = getAPathArgument()
845+
}
842846
}
843847

844848
/**

javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,11 @@ module TaintedPath {
1919
Configuration() { this = "TaintedPath" }
2020

2121
override predicate isSource(DataFlow::Node source, DataFlow::FlowLabel label) {
22-
source instanceof Source and
23-
label instanceof Label::PosixPath
22+
label = source.(Source).getAFlowLabel()
2423
}
2524

2625
override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel label) {
27-
sink instanceof Sink and
28-
label instanceof Label::PosixPath
26+
label = sink.(Sink).getAFlowLabel()
2927
}
3028

3129
override predicate isBarrier(DataFlow::Node node) {

javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,22 @@ module TaintedPath {
1010
/**
1111
* A data flow source for tainted-path vulnerabilities.
1212
*/
13-
abstract class Source extends DataFlow::Node { }
13+
abstract class Source extends DataFlow::Node {
14+
/** Gets a flow label denoting the type of value for which this is a source. */
15+
DataFlow::FlowLabel getAFlowLabel() {
16+
result instanceof Label::PosixPath
17+
}
18+
}
1419

1520
/**
1621
* A data flow sink for tainted-path vulnerabilities.
1722
*/
18-
abstract class Sink extends DataFlow::Node { }
23+
abstract class Sink extends DataFlow::Node {
24+
/** Gets a flow label denoting the type of value for which this is a sink. */
25+
DataFlow::FlowLabel getAFlowLabel() {
26+
result instanceof Label::PosixPath
27+
}
28+
}
1929

2030
/**
2131
* A sanitizer for tainted-path vulnerabilities.
@@ -369,17 +379,36 @@ module TaintedPath {
369379
* A path argument to a file system access.
370380
*/
371381
class FsPathSink extends Sink, DataFlow::ValueNode {
382+
FileSystemAccess fileSystemAccess;
383+
372384
FsPathSink() {
373-
exists(FileSystemAccess fs |
374-
this = fs.getAPathArgument() and
375-
not exists(fs.getRootPathArgument())
385+
(
386+
this = fileSystemAccess.getAPathArgument() and
387+
not exists(fileSystemAccess.getRootPathArgument())
376388
or
377-
this = fs.getRootPathArgument()
389+
this = fileSystemAccess.getRootPathArgument()
378390
) and
379391
not this = any(ResolvingPathCall call).getInput()
380392
}
381393
}
382394

395+
/**
396+
* A path argument to a file system access, which disallows upward navigation.
397+
*/
398+
private class FsPathSinkWithoutUpwardNavigation extends FsPathSink {
399+
FsPathSinkWithoutUpwardNavigation() {
400+
fileSystemAccess.isUpwardNavigationRejected(this)
401+
}
402+
403+
override DataFlow::FlowLabel getAFlowLabel() {
404+
// The protection is ineffective if the ../ segments have already
405+
// cancelled out against the intended root dir using path.join or similar.
406+
// Only flag normalized paths, as this corresponds to the output
407+
// of a normalizing call that had a malicious input.
408+
result.(Label::PosixPath).isNormalized()
409+
}
410+
}
411+
383412
/**
384413
* A path argument to the Express `res.render` method.
385414
*/
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
| normalizedPaths.js:208:35:208:60 | // OK - ... anyway | Spurious alert |
1+
| normalizedPaths.js:208:38:208:63 | // OK - ... anyway | Spurious alert |

0 commit comments

Comments
 (0)