Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* The `response.download()` function in `express` is now recognized as a sink for path traversal attacks.
17 changes: 17 additions & 0 deletions javascript/ql/lib/semmle/javascript/frameworks/Express.qll
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,23 @@ module Express {
}
}

/** A call to `response.download`, considered as a file system access. */
private class ResponseDownloadAsFileSystemAccess extends FileSystemReadAccess,
DataFlow::MethodCallNode
{
ResponseDownloadAsFileSystemAccess() {
exists(string name | name = "download" | this.calls(any(ResponseNode res), name))
}

override DataFlow::Node getADataNode() { none() }

override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }

override DataFlow::Node getRootPathArgument() {
result = this.(DataFlow::CallNode).getOptionArgument([1, 2], "root")
}
}

/**
* A function that flows to a route setup.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,12 @@ nodes
| tainted-sendFile.js:24:37:24:48 | req.params.x | semmle.label | req.params.x |
| tainted-sendFile.js:25:16:25:46 | path.jo ... rams.x) | semmle.label | path.jo ... rams.x) |
| tainted-sendFile.js:25:34:25:45 | req.params.x | semmle.label | req.params.x |
| tainted-sendFile.js:30:16:30:33 | req.param("gimme") | semmle.label | req.param("gimme") |
| tainted-sendFile.js:33:16:33:48 | homeDir ... arams.x | semmle.label | homeDir ... arams.x |
| tainted-sendFile.js:33:37:33:48 | req.params.x | semmle.label | req.params.x |
| tainted-sendFile.js:35:16:35:46 | path.jo ... rams.x) | semmle.label | path.jo ... rams.x) |
| tainted-sendFile.js:35:34:35:45 | req.params.x | semmle.label | req.params.x |
| tainted-sendFile.js:38:43:38:58 | req.param("dir") | semmle.label | req.param("dir") |
| tainted-string-steps.js:6:7:6:48 | path | semmle.label | path |
| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | semmle.label | url.par ... , true) |
| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | semmle.label | url.par ... ).query |
Expand Down Expand Up @@ -895,6 +901,8 @@ edges
| tainted-promise-steps.js:12:20:12:23 | path | tainted-promise-steps.js:12:44:12:47 | path | provenance | |
| tainted-sendFile.js:24:37:24:48 | req.params.x | tainted-sendFile.js:24:16:24:49 | path.re ... rams.x) | provenance | Config |
| tainted-sendFile.js:25:34:25:45 | req.params.x | tainted-sendFile.js:25:16:25:46 | path.jo ... rams.x) | provenance | Config |
| tainted-sendFile.js:33:37:33:48 | req.params.x | tainted-sendFile.js:33:16:33:48 | homeDir ... arams.x | provenance | Config |
| tainted-sendFile.js:35:34:35:45 | req.params.x | tainted-sendFile.js:35:16:35:46 | path.jo ... rams.x) | provenance | Config |
| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | provenance | |
| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | provenance | |
| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | provenance | |
Expand Down Expand Up @@ -1114,6 +1122,10 @@ subpaths
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,18 @@ app.get('/some/path/:x', function(req, res) {
res.sendfile(path.join('data', req.params.x)); // NOT OK

res.sendFile(homeDir + path.join('data', req.params.x)); // kinda OK - can only escape from 'data/'

// BAD: downloading a file based on un-sanitized query parameters
res.download(req.param("gimme"));

// BAD: download allows ../
res.download(homeDir + '/data/' + req.params.x);

res.download(path.join('data', req.params.x)); // NOT OK

// BAD: doesn't help if user controls root
res.download(req.param("file"), { root: req.param("dir") });

// GOOD: ensures files cannot be accessed outside of root folder
res.download(req.param("gimme"), { root: process.cwd() });
});