From 43c52a8b4f844e91b02e3cc9df92826a2d7b6052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Fri, 13 Jun 2025 12:53:00 +0200 Subject: [PATCH] fix(core): Prevent DoS via malformed binary data ID (#16229) --- .../__tests__/binary-data.controller.test.ts | 22 +++++++++++++++++-- .../src/controllers/binary-data.controller.ts | 15 ++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/controllers/__tests__/binary-data.controller.test.ts b/packages/cli/src/controllers/__tests__/binary-data.controller.test.ts index 16d1f04e62..960e078a2a 100644 --- a/packages/cli/src/controllers/__tests__/binary-data.controller.test.ts +++ b/packages/cli/src/controllers/__tests__/binary-data.controller.test.ts @@ -26,7 +26,7 @@ describe('BinaryDataController', () => { await controller.get(request, response, query); expect(response.status).toHaveBeenCalledWith(400); - expect(response.end).toHaveBeenCalledWith('Missing binary data mode'); + expect(response.end).toHaveBeenCalledWith('Malformed binary data ID'); }); it('should return 400 if binary data mode is invalid', async () => { @@ -152,6 +152,24 @@ describe('BinaryDataController', () => { expect(result).toBe(stream); expect(binaryDataService.getAsStream).toHaveBeenCalledWith('filesystem:123'); }); + + describe('with malicious binary data IDs', () => { + it.each([ + ['filesystem:'], + ['filesystem-v2:'], + ['filesystem:/'], + ['filesystem-v2:/'], + ['filesystem://'], + ['filesystem-v2://'], + ])('should return 400 for ID "%s"', async (maliciousId) => { + const query = { id: maliciousId, action: 'download' } as BinaryDataQueryDto; + + await controller.get(request, response, query); + + expect(response.status).toHaveBeenCalledWith(400); + expect(response.end).toHaveBeenCalledWith('Malformed binary data ID'); + }); + }); }); describe('getSigned', () => { @@ -162,7 +180,7 @@ describe('BinaryDataController', () => { await controller.getSigned(request, response, query); expect(response.status).toHaveBeenCalledWith(400); - expect(response.end).toHaveBeenCalledWith('Missing binary data mode'); + expect(response.end).toHaveBeenCalledWith('Malformed binary data ID'); }); it('should return 400 if binary data mode is invalid', async () => { diff --git a/packages/cli/src/controllers/binary-data.controller.ts b/packages/cli/src/controllers/binary-data.controller.ts index f6344da6df..134e4bbf36 100644 --- a/packages/cli/src/controllers/binary-data.controller.ts +++ b/packages/cli/src/controllers/binary-data.controller.ts @@ -47,14 +47,23 @@ export class BinaryDataController { throw new BadRequestError('Missing binary data ID'); } - if (!binaryDataId.includes(':')) { - throw new BadRequestError('Missing binary data mode'); + const separatorIndex = binaryDataId.indexOf(':'); + + if (separatorIndex === -1) { + throw new BadRequestError('Malformed binary data ID'); } - const [mode] = binaryDataId.split(':'); + const mode = binaryDataId.substring(0, separatorIndex); + if (!isValidNonDefaultMode(mode)) { throw new BadRequestError('Invalid binary data mode'); } + + const path = binaryDataId.substring(separatorIndex + 1); + + if (path === '' || path === '/' || path === '//') { + throw new BadRequestError('Malformed binary data ID'); + } } private async setContentHeaders(