Skip to content

Commit 286373d

Browse files
authored
fix: Cloud function dispatch crashes server via prototype chain traversal ([GHSA-4263-jgmp-7pf4](GHSA-4263-jgmp-7pf4)) (#10210)
1 parent 96d8703 commit 286373d

File tree

2 files changed

+71
-0
lines changed

2 files changed

+71
-0
lines changed

spec/vulnerabilities.spec.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,71 @@ describe('Vulnerabilities', () => {
251251
});
252252
});
253253

254+
describe('(GHSA-4263-jgmp-7pf4) Cloud function prototype chain dispatch via registered function', () => {
255+
const headers = {
256+
'Content-Type': 'application/json',
257+
'X-Parse-Application-Id': 'test',
258+
'X-Parse-REST-API-Key': 'rest',
259+
};
260+
261+
beforeEach(() => {
262+
Parse.Cloud.define('legitimateFunction', () => 'ok');
263+
});
264+
265+
it('rejects prototype chain traversal from a registered function name', async () => {
266+
const response = await request({
267+
headers,
268+
method: 'POST',
269+
url: 'http://localhost:8378/1/functions/legitimateFunction.__proto__.__proto__.constructor',
270+
body: JSON.stringify({}),
271+
}).catch(e => e);
272+
expect(response.status).toBe(400);
273+
const text = JSON.parse(response.text);
274+
expect(text.code).toBe(Parse.Error.SCRIPT_FAILED);
275+
expect(text.error).toContain('Invalid function');
276+
});
277+
278+
it('rejects prototype chain traversal via single __proto__ from a registered function', async () => {
279+
const response = await request({
280+
headers,
281+
method: 'POST',
282+
url: 'http://localhost:8378/1/functions/legitimateFunction.__proto__.constructor',
283+
body: JSON.stringify({}),
284+
}).catch(e => e);
285+
expect(response.status).toBe(400);
286+
const text = JSON.parse(response.text);
287+
expect(text.code).toBe(Parse.Error.SCRIPT_FAILED);
288+
expect(text.error).toContain('Invalid function');
289+
});
290+
291+
it('does not crash the server when prototype chain traversal is attempted', async () => {
292+
const maliciousNames = [
293+
'legitimateFunction.__proto__.__proto__.constructor',
294+
'legitimateFunction.__proto__.constructor',
295+
'legitimateFunction.constructor',
296+
'legitimateFunction.__proto__',
297+
];
298+
for (const name of maliciousNames) {
299+
const response = await request({
300+
headers,
301+
method: 'POST',
302+
url: `http://localhost:8378/1/functions/${encodeURIComponent(name)}`,
303+
body: JSON.stringify({}),
304+
}).catch(e => e);
305+
expect(response.status).toBe(400);
306+
}
307+
// Verify server is still responsive after all attempts
308+
const healthResponse = await request({
309+
headers,
310+
method: 'POST',
311+
url: 'http://localhost:8378/1/functions/legitimateFunction',
312+
body: JSON.stringify({}),
313+
});
314+
expect(healthResponse.status).toBe(200);
315+
expect(JSON.parse(healthResponse.text).result).toBe('ok');
316+
});
317+
});
318+
254319
describe('(GHSA-3v4q-4q9g-x83q) Prototype pollution via application ID in trigger store', () => {
255320
const prototypeProperties = ['constructor', 'toString', 'valueOf', 'hasOwnProperty', '__proto__'];
256321

src/triggers.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ function getStore(category, name, applicationId) {
109109
_triggerStore[applicationId] = _triggerStore[applicationId] || baseStore();
110110
let store = _triggerStore[applicationId][category];
111111
for (const component of path) {
112+
if (!Object.prototype.hasOwnProperty.call(store, component)) {
113+
return createStore();
114+
}
112115
store = store[component];
113116
if (!store) {
114117
return createStore();
@@ -137,6 +140,9 @@ function remove(category, name, applicationId) {
137140
function get(category, name, applicationId) {
138141
const lastComponent = name.split('.').splice(-1);
139142
const store = getStore(category, name, applicationId);
143+
if (!Object.prototype.hasOwnProperty.call(store, lastComponent)) {
144+
return undefined;
145+
}
140146
return store[lastComponent];
141147
}
142148

0 commit comments

Comments
 (0)