CVE-2023-45820: Improper Handling of Exceptional Conditions (CWE-755)
value: Directus is a real-time API and App dashboard for managing SQL database content. In affected versions any Directus installation that has websockets enabled can be crashed if the websocket server receives an invalid frame. A malicious user could leverage this bug to crash Directus. This issue has been addressed in version 10.6.2. Users are advised to upgrade. Users unable to upgrade should avoid using websockets.
CWE description: The product does not handle or incorrectly handles an exceptional condition.
CVSS v3 base score: 6.5
Published: 2023-10-19T19:15Z
Repository: https://github.com/directus/directus
Language: TypeScript
Files changed: api/src/websocket/controllers/base.ts
Commit date: 2023-09-19 13:12:42 -0400
Commit message: Merge pull request from GHSA-hmgw-9jrg-hf2m

* Add extra error handling for invalid messages

* added description and logging

* renamed fix function

---------

Co-authored-by: Rijk van Zanten <rijkvanzanten@me.com>
Diff: diff --git a/api/src/websocket/controllers/base.ts b/api/src/websocket/controllers/base.ts
index dcf121ed2f..704112518c 100644
--- a/api/src/websocket/controllers/base.ts
+++ b/api/src/websocket/controllers/base.ts
@@ -95,10 +95,24 @@ export default abstract class SocketController {
 		}
 
 		return null;
 	}
 
+	private catchInvalidMessages(ws: WebSocket) {
+		/**
+		 * This fix was done to prevent the API from crashing on receiving invalid WebSocket frames
+		 * https://github.com/directus/directus/security/advisories/GHSA-hmgw-9jrg-hf2m
+		 * https://github.com/websockets/ws/issues/2098
+		 */
+		// @ts-ignore <- required because "_socket" is not typed on WS
+		ws._socket.prependListener('data', (data) => data.toString());
+
+		ws.on('error', (error) => {
+			if (error.message) logger.debug(error.message);
+		});
+	}
+
 	protected async handleUpgrade(request: IncomingMessage, socket: internal.Duplex, head: Buffer) {
 		const { pathname, query } = parse(request.url!, true);
 		if (pathname !== this.endpoint) return;
 
 		if (this.clients.size >= this.maxConnections) {
@@ -119,10 +133,11 @@ export default abstract class SocketController {
 			await this.handleHandshakeUpgrade(context);
 			return;
 		}
 
 		this.server.handleUpgrade(request, socket, head, async (ws) => {
+			this.catchInvalidMessages(ws);
 			const state = { accountability: null, expires_at: null } as AuthenticationState;
 			this.server.emit('connection', ws, state);
 		});
 	}
 
@@ -144,17 +159,20 @@ export default abstract class SocketController {
 			socket.destroy();
 			return;
 		}
 
 		this.server.handleUpgrade(request, socket, head, async (ws) => {
+			this.catchInvalidMessages(ws);
 			const state = { accountability, expires_at } as AuthenticationState;
 			this.server.emit('connection', ws, state);
 		});
 	}
 
 	protected async handleHandshakeUpgrade({ request, socket, head }: UpgradeContext) {
 		this.server.handleUpgrade(request, socket, head, async (ws) => {
+			this.catchInvalidMessages(ws);
+
 			try {
 				const payload = await waitForAnyMessage(ws, this.authentication.timeout);
 				if (getMessageType(payload) !== 'auth') throw new Error();
 
 				const state = await authenticateConnection(WebSocketAuthMessage.parse(payload));

Fixed code: 	private catchInvalidMessages(ws: WebSocket) {
		/**
		 * This fix was done to prevent the API from crashing on receiving invalid WebSocket frames
		 * https://github.com/directus/directus/security/advisories/GHSA-hmgw-9jrg-hf2m
		 * https://github.com/websockets/ws/issues/2098
		 */
		// @ts-ignore <- required because "_socket" is not typed on WS
		ws._socket.prependListener('data', (data) => data.toString());

		ws.on('error', (error) => {
			if (error.message) logger.debug(error.message);
		});
	}

			this.catchInvalidMessages(ws);
			this.catchInvalidMessages(ws);
			this.catchInvalidMessages(ws);