Skip to content

Commit c57f8a6

Browse files
author
Esben Sparre Andreasen
authored
Merge pull request #691 from asger-semmle/sendfile-root
JS: Recognize 'root' option in Express res.sendFile
2 parents 495a1fc + f9da1dc commit c57f8a6

File tree

6 files changed

+29
-2
lines changed

6 files changed

+29
-2
lines changed

change-notes/1.20/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,6 @@
2929
| Uncontrolled data used in network request | More results | This rule now recognizes host values that are vulnerable to injection. |
3030
| Unused parameter | Fewer false-positive results | This rule no longer flags parameters with leading underscore. |
3131
| Unused variable, import, function or class | Fewer false-positive results | This rule now flags fewer variables that are implictly used by JSX elements, and no longer flags variables with leading underscore. |
32+
| Uncontrolled data used in path expression | Fewer false-positive results | This rule now recognizes the Express `root` option, which prevents path traversal. |
3233

3334
## Changes to QL libraries

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,15 @@ abstract class FileSystemAccess extends DataFlow::Node {
2828

2929
/** Gets an argument to this file system access that is interpreted as a path. */
3030
abstract DataFlow::Node getAPathArgument();
31-
31+
32+
/**
33+
* Gets an argument to this file system access that is interpreted as a root folder
34+
* in which the path arguments are constrained.
35+
*
36+
* In other words, if a root argument is provided, the underlying file access does its own
37+
* sanitization to prevent the path arguments from traversing outside the root folder.
38+
*/
39+
DataFlow::Node getRootPathArgument() { none() }
3240
}
3341

3442
/**

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -866,6 +866,10 @@ module Express {
866866
override DataFlow::Node getAPathArgument() {
867867
result = DataFlow::valueNode(astNode.getArgument(0))
868868
}
869+
870+
override DataFlow::Node getRootPathArgument() {
871+
result = this.(DataFlow::CallNode).getOptionArgument(1, "root")
872+
}
869873
}
870874

871875
/**

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,11 @@ module TaintedPath {
7171
*/
7272
class FsPathSink extends Sink, DataFlow::ValueNode {
7373
FsPathSink() {
74-
this = any(FileSystemAccess fs).getAPathArgument()
74+
exists (FileSystemAccess fs |
75+
this = fs.getAPathArgument() and
76+
not exists(fs.getRootPathArgument())
77+
or
78+
this = fs.getRootPathArgument())
7579
}
7680
}
7781

javascript/ql/test/query-tests/Security/CWE-022/TaintedPath.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ nodes
8787
| tainted-require.js:7:19:7:37 | req.param("module") |
8888
| tainted-sendFile.js:7:16:7:33 | req.param("gimme") |
8989
| tainted-sendFile.js:9:16:9:33 | req.param("gimme") |
90+
| tainted-sendFile.js:17:43:17:58 | req.param("dir") |
9091
| views.js:1:43:1:55 | req.params[0] |
9192
edges
9293
| TaintedPath-es6.js:7:7:7:44 | path | TaintedPath-es6.js:10:41:10:44 | path |
@@ -194,4 +195,5 @@ edges
194195
| tainted-require.js:7:19:7:37 | req.param("module") | tainted-require.js:7:19:7:37 | req.param("module") | tainted-require.js:7:19:7:37 | req.param("module") | This path depends on $@. | tainted-require.js:7:19:7:37 | req.param("module") | a user-provided value |
195196
| tainted-sendFile.js:7:16:7:33 | req.param("gimme") | tainted-sendFile.js:7:16:7:33 | req.param("gimme") | tainted-sendFile.js:7:16:7:33 | req.param("gimme") | This path depends on $@. | tainted-sendFile.js:7:16:7:33 | req.param("gimme") | a user-provided value |
196197
| tainted-sendFile.js:9:16:9:33 | req.param("gimme") | tainted-sendFile.js:9:16:9:33 | req.param("gimme") | tainted-sendFile.js:9:16:9:33 | req.param("gimme") | This path depends on $@. | tainted-sendFile.js:9:16:9:33 | req.param("gimme") | a user-provided value |
198+
| tainted-sendFile.js:17:43:17:58 | req.param("dir") | tainted-sendFile.js:17:43:17:58 | req.param("dir") | tainted-sendFile.js:17:43:17:58 | req.param("dir") | This path depends on $@. | tainted-sendFile.js:17:43:17:58 | req.param("dir") | a user-provided value |
197199
| views.js:1:43:1:55 | req.params[0] | views.js:1:43:1:55 | req.params[0] | views.js:1:43:1:55 | req.params[0] | This path depends on $@. | views.js:1:43:1:55 | req.params[0] | a user-provided value |

javascript/ql/test/query-tests/Security/CWE-022/tainted-sendFile.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,12 @@ app.get('/some/path', function(req, res) {
77
res.sendFile(req.param("gimme"));
88
// BAD: same as above
99
res.sendfile(req.param("gimme"));
10+
11+
// GOOD: ensures files cannot be accessed outside of root folder
12+
res.sendFile(req.param("gimme"), { root: process.cwd() });
13+
// GOOD: ensures files cannot be accessed outside of root folder
14+
res.sendfile(req.param("gimme"), { root: process.cwd() });
15+
16+
// BAD: doesn't help if user controls root
17+
res.sendFile(req.param("file"), { root: req.param("dir") });
1018
});

0 commit comments

Comments
 (0)