CVE-2023-30591 (JavaScript, published 2023-09-29T06:15Z)
Description: value: Denial-of-service in NodeBB <= v2.8.10 allows unauthenticated attackers to trigger a crash, when invoking `eventName.startsWith()` or `eventName.toString()`, while processing Socket.IO messages via crafted Socket.IO messages containing array or object type for the event name respectively.
Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-754 \u2014 Improper Check for Unusual or Exceptional Conditions
The product does not check or incorrectly checks for unusual or exceptional conditions that are not expected to occur frequently during day to day operation of the product.
Repository: https://github.com/NodeBB/NodeBB
Files changed:
src/socket.io/index.js
Commit message: fix: don't crash on objects with toString property


Vulnerable code:
	const eventName = payload.data[0];
	if (!eventName) {
		return winston.warn('[socket.io] Empty method name');
	}

	if (typeof eventName !== 'string') {
		const escapedName = validator.escape(String(eventName));
		return callback({ message: `[[error:invalid-event, ${escapedName}]]` });
	}
	const parts = eventName.split('.');
	const namespace = parts[0];
	const methodToCall = parts.reduce((prev, cur) => {
		if (prev !== null && prev[cur] && (!prev.hasOwnProperty || prev.hasOwnProperty(cur))) {
			return prev[cur];
		return null;
	}, Namespaces);
	if (!methodToCall || typeof methodToCall !== 'function') {
		if (process.env.NODE_ENV === 'development') {
			winston.warn(`[socket.io] Unrecognized message: ${eventName}`);
		const escapedName = validator.escape(String(eventName));
		return callback({ message: `[[error:invalid-event, ${escapedName}]]` });
	}
	socket.previousEvents = socket.previousEvents || [];
	socket.previousEvents.push(eventName);
	if (socket.previousEvents.length > 20) {
		socket.previousEvents.shift();
	}
	if (!eventName.startsWith('admin.') && ratelimit.isFlooding(socket)) {
		winston.warn(`[socket.io] Too many emits! Disconnecting uid : ${socket.uid}. Events : ${socket.previousEvents}`);
		return socket.disconnect();
	}
	try {

Fixed code:
	let eventName = payload.data[0];
	try {
		if (!eventName) {
			return winston.warn('[socket.io] Empty method name');
		}
		if (typeof eventName !== 'string') {
			eventName = typeof eventName;
			const escapedName = validator.escape(eventName);
			return callback({ message: `[[error:invalid-event, ${escapedName}]]` });
		const parts = eventName.split('.');
		const namespace = parts[0];
		const methodToCall = parts.reduce((prev, cur) => {
			if (prev !== null && prev[cur] && (!prev.hasOwnProperty || prev.hasOwnProperty(cur))) {
				return prev[cur];
			}
			return null;
		}, Namespaces);

		if (!methodToCall || typeof methodToCall !== 'function') {
			if (process.env.NODE_ENV === 'development') {
				winston.warn(`[socket.io] Unrecognized message: ${eventName}`);
			}
			const escapedName = validator.escape(String(eventName));
			return callback({ message: `[[error:invalid-event, ${escapedName}]]` });
		socket.previousEvents = socket.previousEvents || [];
		socket.previousEvents.push(eventName);
		if (socket.previousEvents.length > 20) {
			socket.previousEvents.shift();
		}
		if (!eventName.startsWith('admin.') && ratelimit.isFlooding(socket)) {
			winston.warn(`[socket.io] Too many emits! Disconnecting uid : ${socket.uid}. Events : ${socket.previousEvents}`);
			return socket.disconnect();
		}