Skip to content

Commit 7d9956e

Browse files
authored
Merge pull request #2675 from erik-krogh/WebSocket
Approved by esbena
2 parents 0180672 + 148ec9a commit 7d9956e

File tree

8 files changed

+277
-0
lines changed

8 files changed

+277
-0
lines changed

change-notes/1.24/analysis-javascript.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
- [Electron](https://electronjs.org/)
1717
- [Node.js](https://nodejs.org/)
1818
- [Socket.IO](https://socket.io/)
19+
- [ws](https://github.com/websockets/ws)
20+
- [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API)
1921

2022
## New queries
2123

javascript/ql/src/javascript.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ import semmle.javascript.frameworks.TorrentLibraries
9696
import semmle.javascript.frameworks.Typeahead
9797
import semmle.javascript.frameworks.UriLibraries
9898
import semmle.javascript.frameworks.Vue
99+
import semmle.javascript.frameworks.WebSocket
99100
import semmle.javascript.frameworks.XmlParsers
100101
import semmle.javascript.frameworks.xUnit
101102
import semmle.javascript.linters.ESLint
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/**
2+
* Provides classes for working with [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) and [ws](https://github.com/websockets/ws).
3+
*
4+
* The model is based on the EventEmitter model, and there is therefore a
5+
* data-flow step from where a WebSocket event is sent to where the message
6+
* is received.
7+
*
8+
* Data flow is modeled both from clients to servers, and from servers to clients.
9+
* The model models that clients can send messages to all servers, and servers can send messages to all clients.
10+
*/
11+
12+
import javascript
13+
14+
/**
15+
* Gets the channel name used throughout this WebSocket model.
16+
* WebSockets don't have a concept of channels, and therefore a singleton name is used.
17+
* The name can be anything, as long as it is used consistently in this WebSocket model.
18+
*/
19+
private string channelName() { result = "message" }
20+
21+
/**
22+
* Provides classes that model WebSockets clients.
23+
*/
24+
module ClientWebSocket {
25+
/**
26+
* A class that can be used to instantiate a WebSocket instance.
27+
*/
28+
class SocketClass extends DataFlow::SourceNode {
29+
boolean isNode;
30+
31+
SocketClass() {
32+
this = DataFlow::globalVarRef("WebSocket") and isNode = false
33+
or
34+
this = DataFlow::moduleImport("ws") and isNode = true
35+
}
36+
37+
/**
38+
* Holds if this class is an import of the "ws" module.
39+
*/
40+
predicate isNode() { isNode = true }
41+
}
42+
43+
/**
44+
* A client WebSocket instance.
45+
*/
46+
class ClientSocket extends EventEmitter::Range, DataFlow::SourceNode {
47+
SocketClass socketClass;
48+
49+
ClientSocket() { this = socketClass.getAnInstantiation() }
50+
51+
/**
52+
* Holds if this ClientSocket is created from the "ws" module.
53+
*
54+
* The predicate is used to differentiate where the behavior of the "ws" module differs from the native WebSocket in browsers.
55+
*/
56+
predicate isNode() { socketClass.isNode() }
57+
}
58+
59+
/**
60+
* A message sent from a WebSocket client.
61+
*/
62+
class SendNode extends EventDispatch::Range, DataFlow::CallNode {
63+
override ClientSocket emitter;
64+
65+
SendNode() { this = emitter.getAMemberCall("send") }
66+
67+
override string getChannel() { result = channelName() }
68+
69+
override DataFlow::Node getSentItem(int i) { i = 0 and result = this.getArgument(0) }
70+
71+
override ServerWebSocket::ReceiveNode getAReceiver() { any() }
72+
}
73+
74+
/**
75+
* A handler that is registered to receive messages from a WebSocket.
76+
*/
77+
abstract class ReceiveNode extends EventRegistration::Range, DataFlow::FunctionNode {
78+
override ClientSocket emitter;
79+
80+
override string getChannel() { result = channelName() }
81+
}
82+
83+
/**
84+
* Gets a handler, that is registered using method `methodName` and receives messages sent to `emitter`.
85+
*/
86+
private DataFlow::FunctionNode getAMessageHandler(ClientWebSocket::ClientSocket emitter, string methodName) {
87+
exists(DataFlow::CallNode call |
88+
call = emitter.getAMemberCall(methodName) and
89+
call.getArgument(0).mayHaveStringValue("message") and
90+
result = call.getCallback(1)
91+
)
92+
}
93+
94+
/**
95+
* A handler that receives a message using the WebSocket API.
96+
* 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.
97+
* This class therefore models both the WebSocket library, and a subset of the "ws" library.
98+
*/
99+
private class WebSocketReceiveNode extends ClientWebSocket::ReceiveNode {
100+
WebSocketReceiveNode() {
101+
this = getAMessageHandler(emitter, "addEventListener")
102+
or
103+
this = emitter.getAPropertyWrite("onmessage").getRhs()
104+
}
105+
106+
override DataFlow::Node getReceivedItem(int i) {
107+
i = 0 and result = this.getParameter(0).getAPropertyRead("data")
108+
}
109+
}
110+
111+
/**
112+
* A handler that receives a message using the API from the "ws" library.
113+
* The "ws" library additionally implements the WebSocket API, which is modeled in the `WebSocketReceiveNode` class.
114+
*/
115+
private class WSReceiveNode extends ClientWebSocket::ReceiveNode {
116+
WSReceiveNode () {
117+
emitter.isNode() and
118+
this = getAMessageHandler(emitter, EventEmitter::on())
119+
}
120+
121+
override DataFlow::Node getReceivedItem(int i) {
122+
i = 0 and result = this.getParameter(0)
123+
}
124+
}
125+
}
126+
127+
/**
128+
* Provides classes that model WebSocket servers.
129+
*/
130+
module ServerWebSocket {
131+
/**
132+
* A server WebSocket instance.
133+
*/
134+
class ServerSocket extends EventEmitter::Range, DataFlow::SourceNode {
135+
ServerSocket() {
136+
exists(DataFlow::CallNode onCall |
137+
onCall = DataFlow::moduleImport("ws")
138+
.getAConstructorInvocation("Server")
139+
.getAMemberCall(EventEmitter::on()) and
140+
onCall.getArgument(0).mayHaveStringValue("connection")
141+
|
142+
this = onCall.getCallback(1).getParameter(0)
143+
)
144+
}
145+
}
146+
147+
/**
148+
* A message sent from a WebSocket server.
149+
*/
150+
class SendNode extends EventDispatch::Range, DataFlow::CallNode {
151+
override ServerSocket emitter;
152+
153+
SendNode() { this = emitter.getAMemberCall("send") }
154+
155+
override string getChannel() { result = channelName() }
156+
157+
override DataFlow::Node getSentItem(int i) {
158+
i = 0 and
159+
result = getArgument(0)
160+
}
161+
162+
override ClientWebSocket::ReceiveNode getAReceiver() { any() }
163+
}
164+
165+
/**
166+
* A registration of an event handler that receives data from a WebSocket.
167+
*/
168+
class ReceiveNode extends EventRegistration::Range, DataFlow::CallNode {
169+
override ServerSocket emitter;
170+
171+
ReceiveNode() {
172+
this = emitter.getAMemberCall(EventEmitter::on()) and
173+
this.getArgument(0).mayHaveStringValue("message")
174+
}
175+
176+
override string getChannel() { result = channelName() }
177+
178+
override DataFlow::Node getReceivedItem(int i) {
179+
i = 0 and
180+
result = this.getCallback(1).getParameter(0)
181+
}
182+
}
183+
184+
/**
185+
* A data flow node representing data received from a client, viewed as remote user input.
186+
*/
187+
private class ReceivedItemAsRemoteFlow extends RemoteFlowSource {
188+
ReceivedItemAsRemoteFlow() { this = any(ReceiveNode rercv).getReceivedItem(_) }
189+
190+
override string getSourceType() { result = "WebSocket client data" }
191+
192+
override predicate isUserControlledObject() { any() }
193+
}
194+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
(function () {
2+
const socket = new WebSocket('ws://localhost:8080');
3+
4+
socket.addEventListener('open', function (event) {
5+
socket.send('Hi from browser!');
6+
});
7+
8+
socket.addEventListener('message', function (event) {
9+
console.log('Message from server ', event.data);
10+
});
11+
12+
socket.onmessage = function(event) {
13+
console.log("Message from server 2", event.data)
14+
};
15+
})();
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
(function () {
2+
const WebSocket = require('ws');
3+
4+
const ws = new WebSocket('ws://example.org');
5+
6+
ws.on('open', function open() {
7+
ws.send('Hi from client!');
8+
});
9+
10+
ws.on('message', function incoming(data) {
11+
console.log(data);
12+
});
13+
})();
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
(function () {
2+
const WebSocket = require('ws');
3+
4+
const wss = new WebSocket.Server({ port: 8080 });
5+
6+
wss.on('connection', function connection(ws) {
7+
ws.on('message', function incoming(message) {
8+
console.log('received: %s', message);
9+
});
10+
11+
ws.send('Hi from server!');
12+
});
13+
})();
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
clientSocket
2+
| browser.js:2:17:2:52 | new Web ... :8080') |
3+
| client.js:4:13:4:45 | new Web ... e.org') |
4+
clientSend
5+
| browser.js:5:6:5:36 | socket. ... wser!') |
6+
| client.js:7:5:7:30 | ws.send ... ient!') |
7+
clientReceive
8+
| browser.js:8:37:10:2 | functio ... ta);\\n\\t} |
9+
| browser.js:12:21:14:5 | functio ... )\\n } |
10+
| client.js:10:19:12:2 | functio ... ta);\\n\\t} |
11+
serverSocket
12+
| server.js:6:43:6:44 | ws |
13+
serverSend
14+
| server.js:11:5:11:30 | ws.send ... rver!') |
15+
serverReceive
16+
| server.js:7:5:9:6 | ws.on(' ... \\n \\t\\t}) |
17+
taintStep
18+
| browser.js:5:18:5:35 | 'Hi from browser!' | server.js:7:40:7:46 | message |
19+
| client.js:7:13:7:29 | 'Hi from client!' | server.js:7:40:7:46 | message |
20+
| server.js:11:13:11:29 | 'Hi from server!' | browser.js:9:42:9:51 | event.data |
21+
| server.js:11:13:11:29 | 'Hi from server!' | browser.js:13:44:13:53 | event.data |
22+
| server.js:11:13:11:29 | 'Hi from server!' | client.js:10:37:10:40 | data |
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import javascript
2+
3+
query ClientWebSocket::ClientSocket clientSocket() { any() }
4+
5+
query ClientWebSocket::SendNode clientSend() { any() }
6+
7+
query ClientWebSocket::ReceiveNode clientReceive() { any() }
8+
9+
query ServerWebSocket::ServerSocket serverSocket() { any() }
10+
11+
query ServerWebSocket::SendNode serverSend() { any() }
12+
13+
query ServerWebSocket::ReceiveNode serverReceive() { any() }
14+
15+
query predicate taintStep(DataFlow::Node pred, DataFlow::Node succ) {
16+
any(DataFlow::AdditionalFlowStep s).step(pred, succ)
17+
}

0 commit comments

Comments
 (0)