fix: Denial of Service (DoS) and Cloud Function Dispatch Bypass via Prototype Chain Resolution ([GHSA-5j86-7r7m-p8h6](https://github.com/parse-community/parse-server/security/advisories/GHSA-5j86-7r7m-p8h6)) (#10124)

This commit is contained in:
Manuel
2026-03-07 17:38:28 +00:00
committed by GitHub
parent 8c8eb46022
commit 5c2d60a2f3
2 changed files with 103 additions and 8 deletions
+85
View File
@@ -131,6 +131,91 @@ describe('Vulnerabilities', () => {
});
});
describe('(GHSA-5j86-7r7m-p8h6) Cloud function name prototype chain bypass', () => {
const headers = {
'Content-Type': 'application/json',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
};
it('rejects "constructor" as cloud function name', async () => {
const response = await request({
headers,
method: 'POST',
url: 'http://localhost:8378/1/functions/constructor',
body: JSON.stringify({}),
}).catch(e => e);
expect(response.status).toBe(400);
const text = JSON.parse(response.text);
expect(text.code).toBe(Parse.Error.SCRIPT_FAILED);
expect(text.error).toContain('Invalid function');
});
it('rejects "toString" as cloud function name', async () => {
const response = await request({
headers,
method: 'POST',
url: 'http://localhost:8378/1/functions/toString',
body: JSON.stringify({}),
}).catch(e => e);
expect(response.status).toBe(400);
const text = JSON.parse(response.text);
expect(text.code).toBe(Parse.Error.SCRIPT_FAILED);
expect(text.error).toContain('Invalid function');
});
it('rejects "valueOf" as cloud function name', async () => {
const response = await request({
headers,
method: 'POST',
url: 'http://localhost:8378/1/functions/valueOf',
body: JSON.stringify({}),
}).catch(e => e);
expect(response.status).toBe(400);
const text = JSON.parse(response.text);
expect(text.code).toBe(Parse.Error.SCRIPT_FAILED);
expect(text.error).toContain('Invalid function');
});
it('rejects "hasOwnProperty" as cloud function name', async () => {
const response = await request({
headers,
method: 'POST',
url: 'http://localhost:8378/1/functions/hasOwnProperty',
body: JSON.stringify({}),
}).catch(e => e);
expect(response.status).toBe(400);
const text = JSON.parse(response.text);
expect(text.code).toBe(Parse.Error.SCRIPT_FAILED);
expect(text.error).toContain('Invalid function');
});
it('rejects "__proto__.toString" as cloud function name', async () => {
const response = await request({
headers,
method: 'POST',
url: 'http://localhost:8378/1/functions/__proto__.toString',
body: JSON.stringify({}),
}).catch(e => e);
expect(response.status).toBe(400);
const text = JSON.parse(response.text);
expect(text.code).toBe(Parse.Error.SCRIPT_FAILED);
expect(text.error).toContain('Invalid function');
});
it('still executes a legitimately defined cloud function', async () => {
Parse.Cloud.define('legitimateFunction', () => 'hello');
const response = await request({
headers,
method: 'POST',
url: 'http://localhost:8378/1/functions/legitimateFunction',
body: JSON.stringify({}),
});
expect(response.status).toBe(200);
expect(JSON.parse(response.text).result).toBe('hello');
});
});
describe('Request denylist', () => {
describe('(GHSA-q342-9w2p-57fp) Denylist bypass via sibling nested objects', () => {
it('denies _bsontype:Code after a sibling nested object', async () => {
+18 -8
View File
@@ -20,18 +20,28 @@ export const Types = {
const ConnectClassName = '@Connect';
/**
* Creates a prototype-free object for use as a lookup store.
* This prevents prototype chain properties (e.g. `constructor`, `toString`)
* from being resolved as registered handlers when using bracket notation
* for lookups. Always use this instead of `{}` for handler stores.
*/
function createStore() {
return Object.create(null);
}
const baseStore = function () {
const Validators = Object.keys(Types).reduce(function (base, key) {
base[key] = {};
base[key] = createStore();
return base;
}, {});
const Functions = {};
const Jobs = {};
}, createStore());
const Functions = createStore();
const Jobs = createStore();
const LiveQuery = [];
const Triggers = Object.keys(Types).reduce(function (base, key) {
base[key] = {};
base[key] = createStore();
return base;
}, {});
}, createStore());
return Object.freeze({
Functions,
@@ -90,7 +100,7 @@ function getStore(category, name, applicationId) {
const invalidNameRegex = /['"`]/;
if (invalidNameRegex.test(name)) {
// Prevent a malicious user from injecting properties into the store
return {};
return createStore();
}
const path = name.split('.');
@@ -101,7 +111,7 @@ function getStore(category, name, applicationId) {
for (const component of path) {
store = store[component];
if (!store) {
return {};
return createStore();
}
}
return store;