Files
codeql/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll
2022-03-11 11:10:33 +01:00

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() }
}
}