[Security] WebSocket endpoint accepts unauthorized connections
The default WebSocket endpoint ws://localhost:8031/ws accepts unauthorized connections, which allows rough clients to create large numbers of connections and such slowing the agent down (i.e. DOS attack)
@Test
void tesUnauthorized() throws Exception {
CountDownLatch latch = new CountDownLatch(2);
WebSocketEventHandler handler = new WebSocketEventHandler() {
@Override
public void handleRaw(String topic, String message) {
log.info("{}: {}", topic, message);
latch.countDown();
}
};
WebSocket webSocket = getHttpClient().newWebSocket(new Request.Builder()
.url("ws://localhost:8031/ws")
//.header("X-API-Key", ACAPY_API_KEY)
//.header("Authorization", "Bearer " + token)
.build(), new WebSocketListener(null, handler));
try {
Assertions.assertFalse(latch.await(10, TimeUnit.SECONDS), "No messages expected");
} finally {
closeWebSocket(webSocket);
}
}
2022-04-13 09:30:14 INFO [io.nessus.aries.test.websocket.UnauthorizedWebSocketTest] - settings: {"authenticated":false,"label":"Aries Cloud Agent","endpoint":"http://localhost:8030","no_receive_invites":false,"help_link":null}
2022-04-13 09:30:19 INFO [io.nessus.aries.test.websocket.UnauthorizedWebSocketTest] - ping: null
@andrewwhitehead and @TimoGlastra (and others) -- thoughts on this? I believe the primary use of the websockets transport on the ACA-Py side is as a mediator. Thoughts on how to handle this?
I'm also testing websockets in multi tenant mode and what I noticed is the following during ws connection setup:
- No admin key and no bearer token set, directly returns "unauthorised" which is what I would expect
- Only bearer token set, allows to connect but only the settings event is received
- Set both the admin key and a bearer token from any of the subwallets receives all events from all of the subwallets regardless of the wallet dispatch type. I do not know if this is expected.
From what I have seen so far the websocket pretty much behaves as the webhook, at least for single tenant wallets. I hope it is supposed to work that way because it's pretty amazing stuff when writing tests.
@TimoGlastra @ianco -- is that behaviour for 2 and 3 in the previous note what you would expect? This is way beyond my knowledge of multi-tenant.
@TimoGlastra @ianco -- is that behaviour for 2 and 3 in the previous note what you would expect? This is way beyond my knowledge of multi-tenant.
I don't know, I haven't done much work (i.e. any) with websockets. Let me know if you want me to dig into it.
This code, which is now independent of the acapy-java-client, shows a successful WS handshake and the Open message with the Protocol Update - no admin key nor bearer token is set. We then also see the "settings" message from Aca-Py
2022-04-22 11:03:07 INFO [io.nessus.aries.test.websocket.UnauthorizedWebSocketTest] - Open: Response{protocol=http/1.1, code=101, message=Switching Protocols, url=http://localhost:8031/ws}
2022-04-22 11:03:07 INFO [io.nessus.aries.test.websocket.UnauthorizedWebSocketTest] - Message: {"topic": "settings", "payload": {"authenticated": false, "label": "Aries Cloud Agent", "endpoint": "http://localhost:8030", "no_receive_invites": false, "help_link": null}}
2022-04-22 11:03:07 INFO [io.nessus.aries.test.websocket.UnauthorizedWebSocketTest] - Closing: 1000
2022-04-22 11:03:07 INFO [io.nessus.aries.test.websocket.UnauthorizedWebSocketTest] - Closed: 1000
That sub-wallet specific WS connections (i.e.connections that were created with the wallet's access token) see messages for other sub-wallets, may possibly be related to #1742. For example, Acme sees connections handshake messages between Alice and Faber.
From the websockets page
An attacker who can open an arbitrary number of connections will be able to perform a denial of service by memory exhaustion. If you’re concerned by denial of service attacks, you must reject suspicious connections before they reach websockets, typically in a reverse proxy.