CVE-2024-36128: Insufficient Information (NVD-CWE-noinfo)
value: Directus is a real-time API and App dashboard for managing SQL database content. Prior to 10.11.2, providing a non-numeric length value to the random string generation utility will create a memory issue breaking the capability to generate random strings platform wide. This creates a denial of service situation where logged in sessions can no longer be refreshed as sessions depend on the capability to generate a random session ID. This vulnerability is fixed in 10.11.2.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-06-03T15:15Z
Repository: https://github.com/directus/directus
Language: TypeScript
Files changed: .changeset/large-pans-dance.md, api/src/controllers/utils.ts, api/src/services/graphql/index.ts
Commit date: 2024-05-26 09:13:17 -0400
Commit message: Fix #22572 Invalid query for random string (#22573)

* Fix #22572 Invalid query for random string

* Add changeset

* Run prettier

* Fix unit tests warnings

* redo validation with Joi

* make validation on object level

documents itself and is cleaner to expand on

* Update changeset

* Allow integer only

* Require min value of 1

* Report as query error, ignore unknown queries

---------

Co-authored-by: Daniel Biegler <DanielBiegler@users.noreply.github.com>
Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch>
Diff: diff --git a/.changeset/large-pans-dance.md b/.changeset/large-pans-dance.md
new file mode 100644
index 00000000000..9aa01aff6b9
--- /dev/null
+++ b/.changeset/large-pans-dance.md
@@ -0,0 +1,5 @@
+---
+"@directus/api": patch
+---
+
+Fixed an issue where calling `/random/string` with an invalid length param could prevent creation of valid sessions until next restart
diff --git a/api/src/controllers/utils.ts b/api/src/controllers/utils.ts
index f74ae3e8bc6..7ef966d4f66 100644
--- a/api/src/controllers/utils.ts
+++ b/api/src/controllers/utils.ts
@@ -12,22 +12,24 @@ import asyncHandler from '../utils/async-handler.js';
 import { generateHash } from '../utils/generate-hash.js';
 import { sanitizeQuery } from '../utils/sanitize-query.js';
 
 const router = Router();
 
+const randomStringSchema = Joi.object<{ length: number }>({
+	length: Joi.number().integer().min(1).max(500).default(32),
+});
+
 router.get(
 	'/random/string',
 	asyncHandler(async (req, res) => {
 		const { nanoid } = await import('nanoid');
 
-		if (req.query && req.query['length'] && Number(req.query['length']) > 500) {
-			throw new InvalidQueryError({ reason: `"length" can't be more than 500 characters` });
-		}
+		const { error, value } = randomStringSchema.validate(req.query, { allowUnknown: true });
 
-		const string = nanoid(req.query?.['length'] ? Number(req.query['length']) : 32);
+		if (error) throw new InvalidQueryError({ reason: error.message });
 
-		return res.json({ data: string });
+		return res.json({ data: nanoid(value.length) });
 	}),
 );
 
 router.post(
 	'/hash/generate',
diff --git a/api/src/services/graphql/index.ts b/api/src/services/graphql/index.ts
index aaf8bb88292..74a9c82396a 100644
--- a/api/src/services/graphql/index.ts
+++ b/api/src/services/graphql/index.ts
@@ -2534,15 +2534,15 @@ export class GraphQLService {
 					length: GraphQLInt,
 				},
 				resolve: async (_, args) => {
 					const { nanoid } = await import('nanoid');
 
-					if (args['length'] && Number(args['length']) > 500) {
-						throw new InvalidPayloadError({ reason: `"length" can't be more than 500 characters` });
+					if (args['length'] !== undefined && (args['length'] < 1 || args['length'] > 500)) {
+						throw new InvalidPayloadError({ reason: `"length" must be between 1 and 500` });
 					}
 
-					return nanoid(args['length'] ? Number(args['length']) : 32);
+					return nanoid(args['length'] ? args['length'] : 32);
 				},
 			},
 			utils_hash_generate: {
 				type: GraphQLString,
 				args: {

Vulnerable code: 		if (req.query && req.query['length'] && Number(req.query['length']) > 500) {
			throw new InvalidQueryError({ reason: `"length" can't be more than 500 characters` });
		}
		const string = nanoid(req.query?.['length'] ? Number(req.query['length']) : 32);
		return res.json({ data: string });
					if (args['length'] && Number(args['length']) > 500) {
						throw new InvalidPayloadError({ reason: `"length" can't be more than 500 characters` });
					return nanoid(args['length'] ? Number(args['length']) : 32);
Fixed code: ---
"@directus/api": patch
---

Fixed an issue where calling `/random/string` with an invalid length param could prevent creation of valid sessions until next restart
const randomStringSchema = Joi.object<{ length: number }>({
	length: Joi.number().integer().min(1).max(500).default(32),
});

		const { error, value } = randomStringSchema.validate(req.query, { allowUnknown: true });
		if (error) throw new InvalidQueryError({ reason: error.message });
		return res.json({ data: nanoid(value.length) });
					if (args['length'] !== undefined && (args['length'] < 1 || args['length'] > 500)) {
						throw new InvalidPayloadError({ reason: `"length" must be between 1 and 500` });
					return nanoid(args['length'] ? args['length'] : 32);