Skip to content

JavaScript: add support for NodeJS and Electron http client libraries #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 20, 2018
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
1 change: 1 addition & 0 deletions change-notes/1.18/analysis-javascript.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
50 changes: 50 additions & 0 deletions javascript/ql/src/semmle/javascript/frameworks/Electron.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
227 changes: 227 additions & 0 deletions javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
| electron.js:7:5:7:38 | net.req ... e.com') |
| electron.js:8:16:8:78 | new Cli ... POST'}) |
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import javascript

from NodeJSLib::ClientRequest cr
select cr
Original file line number Diff line number Diff line change
@@ -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 |
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import javascript

from RemoteFlowSource source
select source
33 changes: 31 additions & 2 deletions javascript/ql/test/library-tests/frameworks/Electron/electron.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,33 @@
const {BrowserView, BrowserWindow} = require('electron')
const {BrowserView, BrowserWindow, ClientRequest, net} = require('electron')

new BrowserWindow({webPreferences: {}})
new BrowserView({webPreferences: {}})
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');
}
Original file line number Diff line number Diff line change
@@ -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" }) |
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import javascript

from NodeJSLib::ClientRequest cr
select cr
Original file line number Diff line number Diff line change
@@ -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} |
Original file line number Diff line number Diff line change
@@ -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' |
Original file line number Diff line number Diff line change
@@ -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 |
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import javascript

from RemoteFlowSource source
select source
Loading