From c59e6fef80949a437f69427a091ab52ae01ac595 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 26 Feb 2021 10:54:46 +0100 Subject: [PATCH] add model for form-data --- .../change-notes/2021-02-26-form-data.md | 4 +++ .../javascript/frameworks/ClientRequests.qll | 20 ++++++++++++++ .../ClientRequests/ClientRequests.expected | 11 ++++++++ .../frameworks/ClientRequests/tst.js | 26 ++++++++++++++++++- 4 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 javascript/change-notes/2021-02-26-form-data.md diff --git a/javascript/change-notes/2021-02-26-form-data.md b/javascript/change-notes/2021-02-26-form-data.md new file mode 100644 index 00000000000..00ad017fad1 --- /dev/null +++ b/javascript/change-notes/2021-02-26-form-data.md @@ -0,0 +1,4 @@ +lgtm,codescanning +* URIs used in the form-data library are now recognized as sinks for `js/request-forgery`. + Affected packages are + [form-data](https://www.npmjs.com/package/form-data) \ No newline at end of file diff --git a/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll b/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll index ebfdc874f64..451ad3f24d2 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll @@ -854,4 +854,24 @@ module ClientRequest { override DataFlow::Node getADataNode() { none() } } } + + /** + * A model of a URL request made using [form-data](https://www.npmjs.com/package/form-data). + */ + class FormDataRequest extends ClientRequest::Range, API::InvokeNode { + API::Node form; + + FormDataRequest() { + form = API::moduleImport("form-data").getInstance() and + this = form.getMember("submit").getACall() + } + + override DataFlow::Node getUrl() { result = getArgument(0) } + + override DataFlow::Node getHost() { result = getParameter(0).getMember("host").getARhs() } + + override DataFlow::Node getADataNode() { + result = form.getMember("append").getACall().getParameter(1).getARhs() + } + } } diff --git a/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected index 47c340f7a8e..dcd76c5a68d 100644 --- a/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected +++ b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected @@ -76,6 +76,9 @@ test_ClientRequest | tst.js:229:5:229:67 | needle( ... ptions) | | tst.js:231:5:233:6 | needle. ... \\n }) | | tst.js:235:5:237:6 | needle. ... \\n }) | +| tst.js:247:24:247:68 | request ... o.png') | +| tst.js:249:1:251:2 | form.su ... e();\\n}) | +| tst.js:257:1:262:2 | form.su ... rs()\\n}) | test_getADataNode | tst.js:53:5:53:23 | axios({data: data}) | tst.js:53:18:53:21 | data | | tst.js:57:5:57:39 | axios.p ... data2}) | tst.js:57:19:57:23 | data1 | @@ -110,12 +113,17 @@ test_getADataNode | tst.js:229:5:229:67 | needle( ... ptions) | tst.js:229:50:229:57 | "MyData" | | tst.js:235:5:237:6 | needle. ... \\n }) | tst.js:228:32:228:70 | { 'X-Cu ... tuna' } | | tst.js:235:5:237:6 | needle. ... \\n }) | tst.js:235:44:235:49 | "data" | +| tst.js:249:1:251:2 | form.su ... e();\\n}) | tst.js:245:25:245:34 | 'my value' | +| tst.js:249:1:251:2 | form.su ... e();\\n}) | tst.js:246:26:246:43 | Buffer.from("foo") | +| tst.js:249:1:251:2 | form.su ... e();\\n}) | tst.js:247:24:247:68 | request ... o.png') | +| tst.js:257:1:262:2 | form.su ... rs()\\n}) | tst.js:255:25:255:35 | 'new_value' | test_getHost | tst.js:87:5:87:39 | http.ge ... host}) | tst.js:87:34:87:37 | host | | tst.js:89:5:89:23 | axios({host: host}) | tst.js:89:18:89:21 | host | | tst.js:91:5:91:34 | got(rel ... host}) | tst.js:91:29:91:32 | host | | tst.js:93:5:93:35 | net.req ... host }) | tst.js:93:29:93:32 | host | | tst.js:219:5:219:41 | data.so ... Host"}) | tst.js:219:32:219:39 | "myHost" | +| tst.js:257:1:262:2 | form.su ... rs()\\n}) | tst.js:259:11:259:23 | 'example.org' | test_getUrl | apollo.js:5:18:5:78 | new cre ... hql' }) | apollo.js:5:44:5:75 | 'https: ... raphql' | | apollo.js:10:1:10:54 | new Htt ... hql' }) | apollo.js:10:21:10:51 | 'http:/ ... raphql' | @@ -199,6 +207,9 @@ test_getUrl | tst.js:229:5:229:67 | needle( ... ptions) | tst.js:229:20:229:47 | "http:/ ... oo/bar" | | tst.js:231:5:233:6 | needle. ... \\n }) | tst.js:231:16:231:35 | "http://example.org" | | tst.js:235:5:237:6 | needle. ... \\n }) | tst.js:235:17:235:41 | "http:/ ... g/post" | +| tst.js:247:24:247:68 | request ... o.png') | tst.js:247:32:247:67 | 'http:/ ... go.png' | +| tst.js:249:1:251:2 | form.su ... e();\\n}) | tst.js:249:13:249:33 | 'http:/ ... e.org/' | +| tst.js:257:1:262:2 | form.su ... rs()\\n}) | tst.js:257:13:262:1 | {\\n m ... ers()\\n} | test_getAResponseDataNode | tst.js:19:5:19:23 | requestPromise(url) | tst.js:19:5:19:23 | requestPromise(url) | text | true | | tst.js:21:5:21:23 | superagent.get(url) | tst.js:21:5:21:23 | superagent.get(url) | stream | true | diff --git a/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js b/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js index a24a0dca791..a843743e53d 100644 --- a/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js +++ b/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js @@ -235,4 +235,28 @@ const needle = require("needle"); needle.post("http://example.org/post", "data", options, (err, resp, body) => { }); -})(); \ No newline at end of file +})(); + +var FormData = require('form-data'); +var request = require('request'); + +var form = new FormData(); + +form.append('my_field', 'my value'); +form.append('my_buffer', Buffer.from("foo")); +form.append('my_logo', request('http://example.org/images/logo.png')); + +form.submit('http://example.org/', (err, res) => { + res.resume(); +}); + + +var form = new FormData(); +form.append('new_form', 'new_value'); + +form.submit({ + method: 'post', + host: 'example.org', + path: '/upload', + headers: form.getHeaders() +}); \ No newline at end of file