Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b5143db

Browse files
authored
Merge pull request #5117 from erik-krogh/parseForm
Approved by asgerf
2 parents 9b8d94d + 74ce736 commit b5143db

6 files changed

Lines changed: 219 additions & 2 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
lgtm,codescanning
2+
* Server side form parsing libraries are now recognized as source of remote user input.
3+
Affected packages are
4+
[multer](https://www.npmjs.com/package/multer),
5+
[busboy](https://www.npmjs.com/package/busboy),
6+
[formidable](https://www.npmjs.com/package/formidable), and
7+
[multiparty](https://www.npmjs.com/package/formidable).

javascript/ql/src/javascript.qll

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,11 @@ import semmle.javascript.frameworks.Electron
8585
import semmle.javascript.frameworks.EventEmitter
8686
import semmle.javascript.frameworks.Files
8787
import semmle.javascript.frameworks.Firebase
88-
import semmle.javascript.frameworks.Immutable
88+
import semmle.javascript.frameworks.FormParsers
8989
import semmle.javascript.frameworks.jQuery
9090
import semmle.javascript.frameworks.JWT
9191
import semmle.javascript.frameworks.Handlebars
92+
import semmle.javascript.frameworks.Immutable
9293
import semmle.javascript.frameworks.LazyCache
9394
import semmle.javascript.frameworks.LodashUnderscore
9495
import semmle.javascript.frameworks.Logging

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -509,8 +509,9 @@ module Express {
509509
this = request.getAPropertyRead("cookies")
510510
or
511511
// `req.files`, treated the same as `req.body`.
512+
// `express-fileupload` uses .files, and `multer` uses .files or .file
512513
kind = "body" and
513-
this = request.getAPropertyRead("files")
514+
this = request.getAPropertyRead(["files", "file"])
514515
)
515516
or
516517
kind = "body" and
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* Provides classes for modelling the server-side form/file parsing libraries.
3+
*/
4+
5+
import javascript
6+
7+
/**
8+
* A source of remote flow from the `Busboy` library.
9+
*/
10+
private class BusBoyRemoteFlow extends RemoteFlowSource {
11+
BusBoyRemoteFlow() {
12+
this =
13+
API::moduleImport("busboy")
14+
.getInstance()
15+
.getMember("on")
16+
.getParameter(1)
17+
.getAParameter()
18+
.getAnImmediateUse()
19+
}
20+
21+
override string getSourceType() { result = "parsed user value from Busbuy" }
22+
}
23+
24+
/**
25+
* A source of remote flow from the `Formidable` library parsing a HTTP request.
26+
*/
27+
private class FormidableRemoteFlow extends RemoteFlowSource {
28+
FormidableRemoteFlow() {
29+
exists(API::Node formidable |
30+
formidable = API::moduleImport("formidable").getReturn()
31+
or
32+
formidable = API::moduleImport("formidable").getMember("formidable").getReturn()
33+
or
34+
formidable =
35+
API::moduleImport("formidable").getMember(["IncomingForm", "Formidable"]).getInstance()
36+
|
37+
this =
38+
formidable.getMember("parse").getACall().getABoundCallbackParameter(1, any(int i | i > 0))
39+
)
40+
}
41+
42+
override string getSourceType() { result = "parsed user value from Formidable" }
43+
}
44+
45+
/**
46+
* A source of remote flow from the `Multiparty` library.
47+
*/
48+
private class MultipartyRemoteFlow extends RemoteFlowSource {
49+
MultipartyRemoteFlow() {
50+
exists(API::Node form | form = API::moduleImport("multiparty").getMember("Form").getInstance() |
51+
exists(API::CallNode parse | parse = form.getMember("parse").getACall() |
52+
this = parse.getParameter(1).getAParameter().getAnImmediateUse()
53+
)
54+
or
55+
exists(API::CallNode on | on = form.getMember("on").getACall() |
56+
on.getArgument(0).mayHaveStringValue(["part", "file", "field"]) and
57+
this = on.getParameter(1).getAParameter().getAnImmediateUse()
58+
)
59+
)
60+
}
61+
62+
override string getSourceType() { result = "parsed user value from Multiparty" }
63+
}

javascript/ql/test/query-tests/Security/CWE-078/CommandInjection.expected

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,47 @@ nodes
8888
| execSeries.js:18:34:18:40 | req.url |
8989
| execSeries.js:19:12:19:16 | [cmd] |
9090
| execSeries.js:19:13:19:15 | cmd |
91+
| form-parsers.js:9:8:9:39 | "touch ... nalname |
92+
| form-parsers.js:9:8:9:39 | "touch ... nalname |
93+
| form-parsers.js:9:19:9:26 | req.file |
94+
| form-parsers.js:9:19:9:26 | req.file |
95+
| form-parsers.js:9:19:9:39 | req.fil ... nalname |
96+
| form-parsers.js:13:3:13:11 | req.files |
97+
| form-parsers.js:13:3:13:11 | req.files |
98+
| form-parsers.js:13:21:13:24 | file |
99+
| form-parsers.js:14:10:14:37 | "touch ... nalname |
100+
| form-parsers.js:14:10:14:37 | "touch ... nalname |
101+
| form-parsers.js:14:21:14:24 | file |
102+
| form-parsers.js:14:21:14:37 | file.originalname |
103+
| form-parsers.js:24:48:24:55 | filename |
104+
| form-parsers.js:24:48:24:55 | filename |
105+
| form-parsers.js:25:10:25:28 | "touch " + filename |
106+
| form-parsers.js:25:10:25:28 | "touch " + filename |
107+
| form-parsers.js:25:21:25:28 | filename |
108+
| form-parsers.js:35:25:35:30 | fields |
109+
| form-parsers.js:35:25:35:30 | fields |
110+
| form-parsers.js:36:10:36:31 | "touch ... ds.name |
111+
| form-parsers.js:36:10:36:31 | "touch ... ds.name |
112+
| form-parsers.js:36:21:36:26 | fields |
113+
| form-parsers.js:36:21:36:31 | fields.name |
114+
| form-parsers.js:40:26:40:31 | fields |
115+
| form-parsers.js:40:26:40:31 | fields |
116+
| form-parsers.js:41:10:41:31 | "touch ... ds.name |
117+
| form-parsers.js:41:10:41:31 | "touch ... ds.name |
118+
| form-parsers.js:41:21:41:26 | fields |
119+
| form-parsers.js:41:21:41:31 | fields.name |
120+
| form-parsers.js:52:34:52:39 | fields |
121+
| form-parsers.js:52:34:52:39 | fields |
122+
| form-parsers.js:53:10:53:31 | "touch ... ds.name |
123+
| form-parsers.js:53:10:53:31 | "touch ... ds.name |
124+
| form-parsers.js:53:21:53:26 | fields |
125+
| form-parsers.js:53:21:53:31 | fields.name |
126+
| form-parsers.js:58:30:58:33 | part |
127+
| form-parsers.js:58:30:58:33 | part |
128+
| form-parsers.js:59:10:59:33 | "touch ... ilename |
129+
| form-parsers.js:59:10:59:33 | "touch ... ilename |
130+
| form-parsers.js:59:21:59:24 | part |
131+
| form-parsers.js:59:21:59:33 | part.filename |
91132
| lib/subLib/index.js:7:32:7:35 | name |
92133
| lib/subLib/index.js:8:10:8:25 | "rm -rf " + name |
93134
| lib/subLib/index.js:8:10:8:25 | "rm -rf " + name |
@@ -222,6 +263,40 @@ edges
222263
| execSeries.js:18:34:18:40 | req.url | execSeries.js:18:13:18:47 | require ... , true) |
223264
| execSeries.js:19:12:19:16 | [cmd] | execSeries.js:13:19:13:26 | commands |
224265
| execSeries.js:19:13:19:15 | cmd | execSeries.js:19:12:19:16 | [cmd] |
266+
| form-parsers.js:9:19:9:26 | req.file | form-parsers.js:9:19:9:39 | req.fil ... nalname |
267+
| form-parsers.js:9:19:9:26 | req.file | form-parsers.js:9:19:9:39 | req.fil ... nalname |
268+
| form-parsers.js:9:19:9:39 | req.fil ... nalname | form-parsers.js:9:8:9:39 | "touch ... nalname |
269+
| form-parsers.js:9:19:9:39 | req.fil ... nalname | form-parsers.js:9:8:9:39 | "touch ... nalname |
270+
| form-parsers.js:13:3:13:11 | req.files | form-parsers.js:13:21:13:24 | file |
271+
| form-parsers.js:13:3:13:11 | req.files | form-parsers.js:13:21:13:24 | file |
272+
| form-parsers.js:13:21:13:24 | file | form-parsers.js:14:21:14:24 | file |
273+
| form-parsers.js:14:21:14:24 | file | form-parsers.js:14:21:14:37 | file.originalname |
274+
| form-parsers.js:14:21:14:37 | file.originalname | form-parsers.js:14:10:14:37 | "touch ... nalname |
275+
| form-parsers.js:14:21:14:37 | file.originalname | form-parsers.js:14:10:14:37 | "touch ... nalname |
276+
| form-parsers.js:24:48:24:55 | filename | form-parsers.js:25:21:25:28 | filename |
277+
| form-parsers.js:24:48:24:55 | filename | form-parsers.js:25:21:25:28 | filename |
278+
| form-parsers.js:25:21:25:28 | filename | form-parsers.js:25:10:25:28 | "touch " + filename |
279+
| form-parsers.js:25:21:25:28 | filename | form-parsers.js:25:10:25:28 | "touch " + filename |
280+
| form-parsers.js:35:25:35:30 | fields | form-parsers.js:36:21:36:26 | fields |
281+
| form-parsers.js:35:25:35:30 | fields | form-parsers.js:36:21:36:26 | fields |
282+
| form-parsers.js:36:21:36:26 | fields | form-parsers.js:36:21:36:31 | fields.name |
283+
| form-parsers.js:36:21:36:31 | fields.name | form-parsers.js:36:10:36:31 | "touch ... ds.name |
284+
| form-parsers.js:36:21:36:31 | fields.name | form-parsers.js:36:10:36:31 | "touch ... ds.name |
285+
| form-parsers.js:40:26:40:31 | fields | form-parsers.js:41:21:41:26 | fields |
286+
| form-parsers.js:40:26:40:31 | fields | form-parsers.js:41:21:41:26 | fields |
287+
| form-parsers.js:41:21:41:26 | fields | form-parsers.js:41:21:41:31 | fields.name |
288+
| form-parsers.js:41:21:41:31 | fields.name | form-parsers.js:41:10:41:31 | "touch ... ds.name |
289+
| form-parsers.js:41:21:41:31 | fields.name | form-parsers.js:41:10:41:31 | "touch ... ds.name |
290+
| form-parsers.js:52:34:52:39 | fields | form-parsers.js:53:21:53:26 | fields |
291+
| form-parsers.js:52:34:52:39 | fields | form-parsers.js:53:21:53:26 | fields |
292+
| form-parsers.js:53:21:53:26 | fields | form-parsers.js:53:21:53:31 | fields.name |
293+
| form-parsers.js:53:21:53:31 | fields.name | form-parsers.js:53:10:53:31 | "touch ... ds.name |
294+
| form-parsers.js:53:21:53:31 | fields.name | form-parsers.js:53:10:53:31 | "touch ... ds.name |
295+
| form-parsers.js:58:30:58:33 | part | form-parsers.js:59:21:59:24 | part |
296+
| form-parsers.js:58:30:58:33 | part | form-parsers.js:59:21:59:24 | part |
297+
| form-parsers.js:59:21:59:24 | part | form-parsers.js:59:21:59:33 | part.filename |
298+
| form-parsers.js:59:21:59:33 | part.filename | form-parsers.js:59:10:59:33 | "touch ... ilename |
299+
| form-parsers.js:59:21:59:33 | part.filename | form-parsers.js:59:10:59:33 | "touch ... ilename |
225300
| lib/subLib/index.js:7:32:7:35 | name | lib/subLib/index.js:8:22:8:25 | name |
226301
| lib/subLib/index.js:8:22:8:25 | name | lib/subLib/index.js:8:10:8:25 | "rm -rf " + name |
227302
| lib/subLib/index.js:8:22:8:25 | name | lib/subLib/index.js:8:10:8:25 | "rm -rf " + name |
@@ -293,6 +368,13 @@ edges
293368
| exec-sh2.js:10:12:10:57 | cp.spaw ... ptions) | exec-sh2.js:14:25:14:31 | req.url | exec-sh2.js:10:40:10:46 | command | This command depends on $@. | exec-sh2.js:14:25:14:31 | req.url | a user-provided value |
294369
| exec-sh.js:15:12:15:61 | cp.spaw ... ptions) | exec-sh.js:19:25:19:31 | req.url | exec-sh.js:15:44:15:50 | command | This command depends on $@. | exec-sh.js:19:25:19:31 | req.url | a user-provided value |
295370
| execSeries.js:14:41:14:47 | command | execSeries.js:18:34:18:40 | req.url | execSeries.js:14:41:14:47 | command | This command depends on $@. | execSeries.js:18:34:18:40 | req.url | a user-provided value |
371+
| form-parsers.js:9:8:9:39 | "touch ... nalname | form-parsers.js:9:19:9:26 | req.file | form-parsers.js:9:8:9:39 | "touch ... nalname | This command depends on $@. | form-parsers.js:9:19:9:26 | req.file | a user-provided value |
372+
| form-parsers.js:14:10:14:37 | "touch ... nalname | form-parsers.js:13:3:13:11 | req.files | form-parsers.js:14:10:14:37 | "touch ... nalname | This command depends on $@. | form-parsers.js:13:3:13:11 | req.files | a user-provided value |
373+
| form-parsers.js:25:10:25:28 | "touch " + filename | form-parsers.js:24:48:24:55 | filename | form-parsers.js:25:10:25:28 | "touch " + filename | This command depends on $@. | form-parsers.js:24:48:24:55 | filename | a user-provided value |
374+
| form-parsers.js:36:10:36:31 | "touch ... ds.name | form-parsers.js:35:25:35:30 | fields | form-parsers.js:36:10:36:31 | "touch ... ds.name | This command depends on $@. | form-parsers.js:35:25:35:30 | fields | a user-provided value |
375+
| form-parsers.js:41:10:41:31 | "touch ... ds.name | form-parsers.js:40:26:40:31 | fields | form-parsers.js:41:10:41:31 | "touch ... ds.name | This command depends on $@. | form-parsers.js:40:26:40:31 | fields | a user-provided value |
376+
| form-parsers.js:53:10:53:31 | "touch ... ds.name | form-parsers.js:52:34:52:39 | fields | form-parsers.js:53:10:53:31 | "touch ... ds.name | This command depends on $@. | form-parsers.js:52:34:52:39 | fields | a user-provided value |
377+
| form-parsers.js:59:10:59:33 | "touch ... ilename | form-parsers.js:58:30:58:33 | part | form-parsers.js:59:10:59:33 | "touch ... ilename | This command depends on $@. | form-parsers.js:58:30:58:33 | part | a user-provided value |
296378
| lib/subLib/index.js:8:10:8:25 | "rm -rf " + name | child_process-test.js:85:37:85:54 | req.query.fileName | lib/subLib/index.js:8:10:8:25 | "rm -rf " + name | This command depends on $@. | child_process-test.js:85:37:85:54 | req.query.fileName | a user-provided value |
297379
| other.js:7:33:7:35 | cmd | other.js:5:25:5:31 | req.url | other.js:7:33:7:35 | cmd | This command depends on $@. | other.js:5:25:5:31 | req.url | a user-provided value |
298380
| other.js:8:28:8:30 | cmd | other.js:5:25:5:31 | req.url | other.js:8:28:8:30 | cmd | This command depends on $@. | other.js:5:25:5:31 | req.url | a user-provided value |
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
var express = require('express');
2+
var multer = require('multer');
3+
var upload = multer({ dest: 'uploads/' });
4+
5+
var app = express();
6+
var exec = require("child_process").exec;
7+
8+
app.post('/profile', upload.single('avatar'), function (req, res, next) {
9+
exec("touch " + req.file.originalname); // NOT OK
10+
});
11+
12+
app.post('/photos/upload', upload.array('photos', 12), function (req, res, next) {
13+
req.files.forEach(file => {
14+
exec("touch " + file.originalname); // NOT OK
15+
})
16+
});
17+
18+
19+
var http = require('http');
20+
var Busboy = require('busboy');
21+
22+
http.createServer(function (req, res) {
23+
var busboy = new Busboy({ headers: req.headers });
24+
busboy.on('file', function (fieldname, file, filename, encoding, mimetype) {
25+
exec("touch " + filename); // NOT OK
26+
});
27+
req.pipe(busboy);
28+
}).listen(8000);
29+
30+
31+
const formidable = require('formidable');
32+
app.post('/api/upload', (req, res, next) => {
33+
let form = formidable({ multiples: true });
34+
35+
form.parse(req, (err, fields, files) => {
36+
exec("touch " + fields.name); // NOT OK
37+
});
38+
39+
let form2 = new formidable.IncomingForm();
40+
form2.parse(req, (err, fields, files) => {
41+
exec("touch " + fields.name); // NOT OK
42+
});
43+
});
44+
45+
var multiparty = require('multiparty');
46+
var http = require('http');
47+
48+
http.createServer(function (req, res) {
49+
// parse a file upload
50+
var form = new multiparty.Form();
51+
52+
form.parse(req, function (err, fields, files) {
53+
exec("touch " + fields.name); // NOT OK
54+
});
55+
56+
57+
var form2 = new multiparty.Form();
58+
form2.on('part', function (part) { // / file / field
59+
exec("touch " + part.filename); // NOT OK
60+
});
61+
form2.parse(req);
62+
63+
}).listen(8080);

0 commit comments

Comments
 (0)