CVE-2023-45820 (published 2023-10-19T19:15Z)

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.

CVSS v3 base score: 6.5, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-755
cwe_name: Improper Handling of Exceptional Conditions
description: The product does not handle or incorrectly handles an exceptional condition.
extended_description: 
            
         
         

Fix information:
repo_url: https://github.com/directus/directus
commit_details: repo_url: https://github.com/directus/directus
author: Brainslug
author_date: 2023-09-19 19:12:42+02:00
msg: 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>
repository: repo_url: https://github.com/directus/directus
repo_name: directus/directus
description: The Modern Data Stack 🐰 — Directus is an instant REST+GraphQL API and intuitive no-code data collaboration app for any SQL database.
repo_language: TypeScript
owner: directus
file_changes: filename: base.ts
change_type: ModificationType.MODIFY
diff: @@ -97,6 +97,20 @@ 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;
@@ -121,6 +135,7 @@ export default abstract class SocketController {
 		}
 
 		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);
 		});
@@ -146,6 +161,7 @@ export default abstract class SocketController {
 		}
 
 		this.server.handleUpgrade(request, socket, head, async (ws) => {
+			this.catchInvalidMessages(ws);
 			const state = { accountability, expires_at } as AuthenticationState;
 			this.server.emit('connection', ws, state);
 		});
@@ -153,6 +169,8 @@ export default abstract class SocketController {
 
 	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();

programming_language: TypeScript