Skip to content

Commit e1c2805

Browse files
authored
Merge pull request #18749 from Kwstubbs/express
JS: Add result.download to Express as Path Traversal Sink
2 parents 9865577 + 253882c commit e1c2805

File tree

4 files changed

+47
-0
lines changed

4 files changed

+47
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* The `response.download()` function in `express` is now recognized as a sink for path traversal attacks.

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

+17
Original file line numberDiff line numberDiff line change
@@ -960,6 +960,23 @@ module Express {
960960
}
961961
}
962962

963+
/** A call to `response.download`, considered as a file system access. */
964+
private class ResponseDownloadAsFileSystemAccess extends FileSystemReadAccess,
965+
DataFlow::MethodCallNode
966+
{
967+
ResponseDownloadAsFileSystemAccess() {
968+
exists(string name | name = "download" | this.calls(any(ResponseNode res), name))
969+
}
970+
971+
override DataFlow::Node getADataNode() { none() }
972+
973+
override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }
974+
975+
override DataFlow::Node getRootPathArgument() {
976+
result = this.(DataFlow::CallNode).getOptionArgument([1, 2], "root")
977+
}
978+
}
979+
963980
/**
964981
* A function that flows to a route setup.
965982
*/

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

+12
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,12 @@ nodes
450450
| tainted-sendFile.js:24:37:24:48 | req.params.x | semmle.label | req.params.x |
451451
| tainted-sendFile.js:25:16:25:46 | path.jo ... rams.x) | semmle.label | path.jo ... rams.x) |
452452
| tainted-sendFile.js:25:34:25:45 | req.params.x | semmle.label | req.params.x |
453+
| tainted-sendFile.js:30:16:30:33 | req.param("gimme") | semmle.label | req.param("gimme") |
454+
| tainted-sendFile.js:33:16:33:48 | homeDir ... arams.x | semmle.label | homeDir ... arams.x |
455+
| tainted-sendFile.js:33:37:33:48 | req.params.x | semmle.label | req.params.x |
456+
| tainted-sendFile.js:35:16:35:46 | path.jo ... rams.x) | semmle.label | path.jo ... rams.x) |
457+
| tainted-sendFile.js:35:34:35:45 | req.params.x | semmle.label | req.params.x |
458+
| tainted-sendFile.js:38:43:38:58 | req.param("dir") | semmle.label | req.param("dir") |
453459
| tainted-string-steps.js:6:7:6:48 | path | semmle.label | path |
454460
| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | semmle.label | url.par ... , true) |
455461
| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | semmle.label | url.par ... ).query |
@@ -895,6 +901,8 @@ edges
895901
| tainted-promise-steps.js:12:20:12:23 | path | tainted-promise-steps.js:12:44:12:47 | path | provenance | |
896902
| tainted-sendFile.js:24:37:24:48 | req.params.x | tainted-sendFile.js:24:16:24:49 | path.re ... rams.x) | provenance | Config |
897903
| tainted-sendFile.js:25:34:25:45 | req.params.x | tainted-sendFile.js:25:16:25:46 | path.jo ... rams.x) | provenance | Config |
904+
| tainted-sendFile.js:33:37:33:48 | req.params.x | tainted-sendFile.js:33:16:33:48 | homeDir ... arams.x | provenance | Config |
905+
| tainted-sendFile.js:35:34:35:45 | req.params.x | tainted-sendFile.js:35:16:35:46 | path.jo ... rams.x) | provenance | Config |
898906
| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | provenance | |
899907
| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | provenance | |
900908
| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | provenance | |
@@ -1114,6 +1122,10 @@ subpaths
11141122
| tainted-sendFile.js:18:43:18:58 | req.param("dir") | tainted-sendFile.js:18:43:18:58 | req.param("dir") | tainted-sendFile.js:18:43:18:58 | req.param("dir") | This path depends on a $@. | tainted-sendFile.js:18:43:18:58 | req.param("dir") | user-provided value |
11151123
| tainted-sendFile.js:24:16:24:49 | path.re ... rams.x) | tainted-sendFile.js:24:37:24:48 | req.params.x | tainted-sendFile.js:24:16:24:49 | path.re ... rams.x) | This path depends on a $@. | tainted-sendFile.js:24:37:24:48 | req.params.x | user-provided value |
11161124
| tainted-sendFile.js:25:16:25:46 | path.jo ... rams.x) | tainted-sendFile.js:25:34:25:45 | req.params.x | tainted-sendFile.js:25:16:25:46 | path.jo ... rams.x) | This path depends on a $@. | tainted-sendFile.js:25:34:25:45 | req.params.x | user-provided value |
1125+
| tainted-sendFile.js:30:16:30:33 | req.param("gimme") | tainted-sendFile.js:30:16:30:33 | req.param("gimme") | tainted-sendFile.js:30:16:30:33 | req.param("gimme") | This path depends on a $@. | tainted-sendFile.js:30:16:30:33 | req.param("gimme") | user-provided value |
1126+
| tainted-sendFile.js:33:16:33:48 | homeDir ... arams.x | tainted-sendFile.js:33:37:33:48 | req.params.x | tainted-sendFile.js:33:16:33:48 | homeDir ... arams.x | This path depends on a $@. | tainted-sendFile.js:33:37:33:48 | req.params.x | user-provided value |
1127+
| tainted-sendFile.js:35:16:35:46 | path.jo ... rams.x) | tainted-sendFile.js:35:34:35:45 | req.params.x | tainted-sendFile.js:35:16:35:46 | path.jo ... rams.x) | This path depends on a $@. | tainted-sendFile.js:35:34:35:45 | req.params.x | user-provided value |
1128+
| tainted-sendFile.js:38:43:38:58 | req.param("dir") | tainted-sendFile.js:38:43:38:58 | req.param("dir") | tainted-sendFile.js:38:43:38:58 | req.param("dir") | This path depends on a $@. | tainted-sendFile.js:38:43:38:58 | req.param("dir") | user-provided value |
11171129
| tainted-string-steps.js:8:18:8:34 | path.substring(4) | tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:8:18:8:34 | path.substring(4) | This path depends on a $@. | tainted-string-steps.js:6:24:6:30 | req.url | user-provided value |
11181130
| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | This path depends on a $@. | tainted-string-steps.js:6:24:6:30 | req.url | user-provided value |
11191131
| tainted-string-steps.js:10:18:10:31 | path.substr(4) | tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:10:18:10:31 | path.substr(4) | This path depends on a $@. | tainted-string-steps.js:6:24:6:30 | req.url | user-provided value |

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

+14
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,18 @@ app.get('/some/path/:x', function(req, res) {
2525
res.sendfile(path.join('data', req.params.x)); // NOT OK
2626

2727
res.sendFile(homeDir + path.join('data', req.params.x)); // kinda OK - can only escape from 'data/'
28+
29+
// BAD: downloading a file based on un-sanitized query parameters
30+
res.download(req.param("gimme"));
31+
32+
// BAD: download allows ../
33+
res.download(homeDir + '/data/' + req.params.x);
34+
35+
res.download(path.join('data', req.params.x)); // NOT OK
36+
37+
// BAD: doesn't help if user controls root
38+
res.download(req.param("file"), { root: req.param("dir") });
39+
40+
// GOOD: ensures files cannot be accessed outside of root folder
41+
res.download(req.param("gimme"), { root: process.cwd() });
2842
});

0 commit comments

Comments
 (0)