mirror of
https://github.com/github/codeql.git
synced 2026-01-25 20:32:58 +01:00
336 lines
10 KiB
Plaintext
336 lines
10 KiB
Plaintext
/**
|
|
* Provides classes for working with [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket), [ws](https://github.com/websockets/ws), and [SockJS](http://sockjs.org).
|
|
*
|
|
* The model is based on the EventEmitter model, and there is therefore a
|
|
* data-flow step from where a WebSocket event is sent to where the message
|
|
* is received.
|
|
*
|
|
* Data flow is modeled both from clients to servers, and from servers to clients.
|
|
* The model models that clients can send messages to all servers, and servers can send messages to all clients.
|
|
*/
|
|
|
|
import javascript
|
|
|
|
/**
|
|
* Gets the channel name used throughout this WebSocket model.
|
|
* WebSockets don't have a concept of channels, and therefore a singleton name is used.
|
|
* The name can be anything, as long as it is used consistently in this WebSocket model.
|
|
*/
|
|
private string channelName() { result = "message" }
|
|
|
|
/**
|
|
* The names of the libraries modeled in this file.
|
|
*/
|
|
private module LibraryNames {
|
|
string sockjs() { result = "SockJS" }
|
|
|
|
string websocket() { result = "WebSocket" }
|
|
|
|
string ws() { result = "ws" }
|
|
|
|
class LibraryName extends string {
|
|
LibraryName() { this = sockjs() or this = websocket() or this = ws() }
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Holds if the websocket library named `client` can send a message to the library named `server`.
|
|
* Both `client` and `server` are library names defined in `LibraryNames`.
|
|
*/
|
|
private predicate areLibrariesCompatible(
|
|
LibraryNames::LibraryName client, LibraryNames::LibraryName server
|
|
) {
|
|
// sockjs is a WebSocket emulating library, but not actually an implementation of WebSockets.
|
|
client = LibraryNames::sockjs() and server = LibraryNames::sockjs()
|
|
or
|
|
server = LibraryNames::ws() and
|
|
(client = LibraryNames::ws() or client = LibraryNames::websocket())
|
|
}
|
|
|
|
/**
|
|
* Provides classes that model WebSockets clients.
|
|
*/
|
|
module ClientWebSocket {
|
|
private import LibraryNames
|
|
|
|
/**
|
|
* A class that can be used to instantiate a WebSocket instance.
|
|
*/
|
|
class SocketClass extends DataFlow::SourceNode {
|
|
LibraryName library; // the name of the WebSocket library. Can be one of the libraries defined in `LibraryNames`.
|
|
|
|
SocketClass() {
|
|
this = DataFlow::globalVarRef("WebSocket") and library = websocket()
|
|
or
|
|
this = DataFlow::moduleImport("ws") and library = ws()
|
|
or
|
|
// the sockjs-client library:https://www.npmjs.com/package/sockjs-client
|
|
library = sockjs() and
|
|
(
|
|
this = DataFlow::moduleImport("sockjs-client") or
|
|
this = DataFlow::globalVarRef("SockJS")
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets the WebSocket library name.
|
|
*/
|
|
LibraryName getLibrary() { result = library }
|
|
}
|
|
|
|
/**
|
|
* A client WebSocket instance.
|
|
*/
|
|
class ClientSocket extends EventEmitter::Range, DataFlow::NewNode, ClientRequest::Range {
|
|
SocketClass socketClass;
|
|
|
|
ClientSocket() { this = socketClass.getAnInstantiation() }
|
|
|
|
/**
|
|
* Gets the WebSocket library name.
|
|
*/
|
|
LibraryName getLibrary() { result = socketClass.getLibrary() }
|
|
|
|
override DataFlow::Node getUrl() { result = this.getArgument(0) }
|
|
|
|
override DataFlow::Node getHost() { none() }
|
|
|
|
override DataFlow::Node getADataNode() {
|
|
exists(SendNode send |
|
|
send.getEmitter() = this and
|
|
result = send.getSentItem(_)
|
|
)
|
|
}
|
|
|
|
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
|
|
responseType = "json" and
|
|
promise = false and
|
|
exists(WebSocketReceiveNode receiver |
|
|
receiver.getEmitter() = this and
|
|
result = receiver.getReceivedItem(_)
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A message sent from a WebSocket client.
|
|
*/
|
|
class SendNode extends EventDispatch::Range, DataFlow::CallNode {
|
|
override ClientSocket emitter;
|
|
|
|
SendNode() { this = emitter.getAMemberCall("send") }
|
|
|
|
override string getChannel() { result = channelName() }
|
|
|
|
override DataFlow::Node getSentItem(int i) { i = 0 and result = this.getArgument(0) }
|
|
|
|
override ServerWebSocket::ReceiveNode getAReceiver() {
|
|
areLibrariesCompatible(emitter.getLibrary(),
|
|
result.getEmitter().(ServerWebSocket::ServerSocket).getLibrary())
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A handler that is registered to receive messages from a WebSocket.
|
|
*/
|
|
abstract class ReceiveNode extends EventRegistration::Range, DataFlow::FunctionNode {
|
|
override ClientSocket emitter;
|
|
|
|
override string getChannel() { result = channelName() }
|
|
}
|
|
|
|
/**
|
|
* Gets a handler, that is registered using method `methodName` and receives messages sent to `emitter`.
|
|
*/
|
|
private DataFlow::FunctionNode getAMessageHandler(
|
|
ClientWebSocket::ClientSocket emitter, string methodName
|
|
) {
|
|
exists(DataFlow::CallNode call |
|
|
call = emitter.getAMemberCall(methodName) and
|
|
call.getArgument(0).mayHaveStringValue("message") and
|
|
result = call.getCallback(1)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A handler that receives a message using the WebSocket API.
|
|
* The WebSocket API is used both by the WebSocket library in browsers, and the same API is also implemented as part of the "ws" library.
|
|
* This class therefore models both the WebSocket library, and a subset of the "ws" library.
|
|
*/
|
|
private class WebSocketReceiveNode extends ClientWebSocket::ReceiveNode {
|
|
WebSocketReceiveNode() {
|
|
this = getAMessageHandler(emitter, "addEventListener")
|
|
or
|
|
this = emitter.getAPropertyWrite("onmessage").getRhs()
|
|
}
|
|
|
|
override DataFlow::Node getReceivedItem(int i) {
|
|
i = 0 and result = this.getParameter(0).getAPropertyRead("data")
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A handler that receives a message using the API from the "ws" library.
|
|
* The "ws" library additionally implements the WebSocket API, which is modeled in the `WebSocketReceiveNode` class.
|
|
*/
|
|
private class WSReceiveNode extends ClientWebSocket::ReceiveNode {
|
|
WSReceiveNode() {
|
|
emitter.getLibrary() = ws() and
|
|
this = getAMessageHandler(emitter, EventEmitter::on())
|
|
}
|
|
|
|
override DataFlow::Node getReceivedItem(int i) { i = 0 and result = this.getParameter(0) }
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Provides classes that model WebSocket servers.
|
|
*/
|
|
module ServerWebSocket {
|
|
private import LibraryNames
|
|
|
|
/**
|
|
* Gets a server created by a library named `library`.
|
|
*/
|
|
DataFlow::SourceNode getAServer(LibraryName library) {
|
|
library = ws() and
|
|
result = DataFlow::moduleImport("ws").getAConstructorInvocation("Server")
|
|
or
|
|
library = sockjs() and
|
|
result = DataFlow::moduleImport("sockjs").getAMemberCall("createServer")
|
|
}
|
|
|
|
/**
|
|
* Gets a `socket.on("connection", (msg, req) => {})` call.
|
|
*/
|
|
private DataFlow::CallNode getAConnectionCall(LibraryName library) {
|
|
result = getAServer(library).getAMemberCall(EventEmitter::on()) and
|
|
result.getArgument(0).mayHaveStringValue("connection")
|
|
}
|
|
|
|
/**
|
|
* A server WebSocket instance.
|
|
*/
|
|
class ServerSocket extends EventEmitter::Range, DataFlow::SourceNode {
|
|
LibraryName library;
|
|
|
|
ServerSocket() { this = getAConnectionCall(library).getCallback(1).getParameter(0) }
|
|
|
|
/**
|
|
* Gets the name of the library that created this server socket.
|
|
*/
|
|
LibraryName getLibrary() { result = library }
|
|
}
|
|
|
|
/**
|
|
* A `socket.on("connection", (msg, req) => {})` call seen as a HTTP route handler.
|
|
* `req` is a `HTTP::IncomingMessage` instance.
|
|
*/
|
|
class ConnectionCallAsRouteHandler extends HTTP::RouteHandler, DataFlow::CallNode {
|
|
ConnectionCallAsRouteHandler() { this = getAConnectionCall(_) }
|
|
|
|
override HTTP::HeaderDefinition getAResponseHeader(string name) { none() }
|
|
}
|
|
|
|
/**
|
|
* The `req` parameter of a `socket.on("connection", (msg, req) => {})` call.
|
|
*/
|
|
class ServerHttpRequest extends HTTP::Servers::RequestSource {
|
|
ConnectionCallAsRouteHandler handler;
|
|
|
|
ServerHttpRequest() { this = handler.getCallback(1).getParameter(1) }
|
|
|
|
override HTTP::RouteHandler getRouteHandler() { result = handler }
|
|
}
|
|
|
|
/** DEPRECATED: Alias for ServerHttpRequest */
|
|
deprecated class ServerHTTPRequest = ServerHttpRequest;
|
|
|
|
/**
|
|
* An access user-controlled HTTP request input in a request to a WebSocket server.
|
|
*/
|
|
class WebSocketRequestInput extends HTTP::RequestInputAccess {
|
|
ServerHttpRequest request;
|
|
string kind;
|
|
|
|
WebSocketRequestInput() {
|
|
kind = "url" and
|
|
this = request.ref().getAPropertyRead("url")
|
|
or
|
|
kind = "header" and
|
|
this = request.ref().getAPropertyRead(["headers", "rawHeaders"]).getAPropertyRead()
|
|
or
|
|
// req.headers.cookie
|
|
kind = "cookie" and
|
|
this = request.ref().getAPropertyRead("headers").getAPropertyRead("cookie")
|
|
}
|
|
|
|
override string getKind() { result = kind }
|
|
|
|
override HTTP::RouteHandler getRouteHandler() { result = request.getRouteHandler() }
|
|
}
|
|
|
|
/**
|
|
* A message sent from a WebSocket server.
|
|
*/
|
|
class SendNode extends EventDispatch::Range, DataFlow::CallNode {
|
|
override ServerSocket emitter;
|
|
|
|
SendNode() {
|
|
emitter.getLibrary() = ws() and
|
|
this = emitter.getAMemberCall("send")
|
|
or
|
|
emitter.getLibrary() = sockjs() and
|
|
this = emitter.getAMemberCall("write")
|
|
}
|
|
|
|
override string getChannel() { result = channelName() }
|
|
|
|
override DataFlow::Node getSentItem(int i) {
|
|
i = 0 and
|
|
result = this.getArgument(0)
|
|
}
|
|
|
|
override ClientWebSocket::ReceiveNode getAReceiver() {
|
|
areLibrariesCompatible(result.getEmitter().(ClientWebSocket::ClientSocket).getLibrary(),
|
|
emitter.getLibrary())
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A registration of an event handler that receives data from a WebSocket.
|
|
*/
|
|
class ReceiveNode extends EventRegistration::Range, DataFlow::CallNode {
|
|
override ServerSocket emitter;
|
|
|
|
ReceiveNode() {
|
|
exists(string eventName |
|
|
emitter.getLibrary() = ws() and eventName = "message"
|
|
or
|
|
emitter.getLibrary() = sockjs() and eventName = "data"
|
|
|
|
|
this = emitter.getAMemberCall(EventEmitter::on()) and
|
|
this.getArgument(0).mayHaveStringValue(eventName)
|
|
)
|
|
}
|
|
|
|
override string getChannel() { result = channelName() }
|
|
|
|
override DataFlow::Node getReceivedItem(int i) {
|
|
i = 0 and
|
|
result = this.getCallback(1).getParameter(0)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A data flow node representing data received from a client, viewed as remote user input.
|
|
*/
|
|
private class ReceivedItemAsRemoteFlow extends RemoteFlowSource {
|
|
ReceivedItemAsRemoteFlow() { this = any(ReceiveNode rercv).getReceivedItem(_) }
|
|
|
|
override string getSourceType() { result = "WebSocket client data" }
|
|
|
|
override predicate isUserControlledObject() { any() }
|
|
}
|
|
}
|