From 9f6e3429d3b326cf4e2994733c618d08032fac6e Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Tue, 19 Mar 2024 17:42:00 +0100 Subject: [PATCH] fix: Server crashes on invalid Cloud Function or Cloud Job name; fixes security vulnerability [GHSA-6hh7-46r2-vf29](https://github.com/parse-community/parse-server/security/advisories/GHSA-6hh7-46r2-vf29) (#9024) --- spec/ParseHooks.spec.js | 33 +++++++++++++++++++++++++++++++++ src/triggers.js | 8 +++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/spec/ParseHooks.spec.js b/spec/ParseHooks.spec.js index 16a2e17be3..8d7c653fa2 100644 --- a/spec/ParseHooks.spec.js +++ b/spec/ParseHooks.spec.js @@ -694,3 +694,36 @@ describe('triggers', () => { expect(req.context).toBeUndefined(); }); }); + +describe('sanitizing names', () => { + const invalidNames = [ + `test'%3bdeclare%20@q%20varchar(99)%3bset%20@q%3d'%5c%5cxxxxxxxxxxxxxxx.yyyyy'%2b'fy.com%5cxus'%3b%20exec%20master.dbo.xp_dirtree%20@q%3b--%20`, + `test.function.name`, + ]; + + it('should not crash server and return error on invalid Cloud Function name', async () => { + for (const invalidName of invalidNames) { + let error; + try { + await Parse.Cloud.run(invalidName); + } catch (err) { + error = err; + } + expect(error).toBeDefined(); + expect(error.message).toMatch(/Invalid function/); + } + }); + + it('should not crash server and return error on invalid Cloud Job name', async () => { + for (const invalidName of invalidNames) { + let error; + try { + await Parse.Cloud.startJob(invalidName); + } catch (err) { + error = err; + } + expect(error).toBeDefined(); + expect(error.message).toMatch(/Invalid job/); + } + }); +}); diff --git a/src/triggers.js b/src/triggers.js index bfebde2e83..34ace35b4b 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -86,6 +86,12 @@ const Category = { }; function getStore(category, name, applicationId) { + const invalidNameRegex = /['"`]/; + if (invalidNameRegex.test(name)) { + // Prevent a malicious user from injecting properties into the store + return {}; + } + const path = name.split('.'); path.splice(-1); // remove last component applicationId = applicationId || Parse.applicationId; @@ -94,7 +100,7 @@ function getStore(category, name, applicationId) { for (const component of path) { store = store[component]; if (!store) { - return undefined; + return {}; } } return store;