Skip to content

Commit ea68fc0

Browse files
authored
fix: Session update endpoint allows overwriting server-generated session fields ([GHSA-jc39-686j-wp6q](GHSA-jc39-686j-wp6q)) (#10263)
1 parent 94637aa commit ea68fc0

File tree

2 files changed

+141
-0
lines changed

2 files changed

+141
-0
lines changed

spec/ParseSession.spec.js

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,143 @@ describe('Parse.Session', () => {
257257
expect(newSession.createdWith.authProvider).toBeUndefined();
258258
});
259259

260+
it('should reject expiresAt when updating a session via PUT', async () => {
261+
const user = await Parse.User.signUp('sessionupdateuser1', 'password');
262+
const sessionToken = user.getSessionToken();
263+
264+
// Get the session objectId
265+
const sessionRes = await request({
266+
method: 'GET',
267+
url: 'http://localhost:8378/1/sessions/me',
268+
headers: {
269+
'X-Parse-Application-Id': 'test',
270+
'X-Parse-REST-API-Key': 'rest',
271+
'X-Parse-Session-Token': sessionToken,
272+
},
273+
});
274+
const sessionId = sessionRes.data.objectId;
275+
const originalExpiresAt = sessionRes.data.expiresAt;
276+
277+
// Attempt to overwrite expiresAt via PUT
278+
const updateRes = await request({
279+
method: 'PUT',
280+
url: `http://localhost:8378/1/sessions/${sessionId}`,
281+
headers: {
282+
'X-Parse-Application-Id': 'test',
283+
'X-Parse-REST-API-Key': 'rest',
284+
'X-Parse-Session-Token': sessionToken,
285+
'Content-Type': 'application/json',
286+
},
287+
body: {
288+
expiresAt: { __type: 'Date', iso: '2099-12-31T23:59:59.000Z' },
289+
},
290+
}).catch(e => e);
291+
292+
expect(updateRes.data.code).toBe(Parse.Error.INVALID_KEY_NAME);
293+
294+
// Verify expiresAt was not changed
295+
const verifyRes = await request({
296+
method: 'GET',
297+
url: 'http://localhost:8378/1/sessions/me',
298+
headers: {
299+
'X-Parse-Application-Id': 'test',
300+
'X-Parse-REST-API-Key': 'rest',
301+
'X-Parse-Session-Token': sessionToken,
302+
},
303+
});
304+
expect(verifyRes.data.expiresAt).toEqual(originalExpiresAt);
305+
});
306+
307+
it('should reject createdWith when updating a session via PUT', async () => {
308+
const user = await Parse.User.signUp('sessionupdateuser2', 'password');
309+
const sessionToken = user.getSessionToken();
310+
311+
// Get the session objectId
312+
const sessionRes = await request({
313+
method: 'GET',
314+
url: 'http://localhost:8378/1/sessions/me',
315+
headers: {
316+
'X-Parse-Application-Id': 'test',
317+
'X-Parse-REST-API-Key': 'rest',
318+
'X-Parse-Session-Token': sessionToken,
319+
},
320+
});
321+
const sessionId = sessionRes.data.objectId;
322+
const originalCreatedWith = sessionRes.data.createdWith;
323+
324+
// Attempt to overwrite createdWith via PUT
325+
const updateRes = await request({
326+
method: 'PUT',
327+
url: `http://localhost:8378/1/sessions/${sessionId}`,
328+
headers: {
329+
'X-Parse-Application-Id': 'test',
330+
'X-Parse-REST-API-Key': 'rest',
331+
'X-Parse-Session-Token': sessionToken,
332+
'Content-Type': 'application/json',
333+
},
334+
body: {
335+
createdWith: { action: 'attacker', authProvider: 'evil' },
336+
},
337+
}).catch(e => e);
338+
339+
expect(updateRes.data.code).toBe(Parse.Error.INVALID_KEY_NAME);
340+
341+
// Verify createdWith was not changed
342+
const verifyRes = await request({
343+
method: 'GET',
344+
url: 'http://localhost:8378/1/sessions/me',
345+
headers: {
346+
'X-Parse-Application-Id': 'test',
347+
'X-Parse-REST-API-Key': 'rest',
348+
'X-Parse-Session-Token': sessionToken,
349+
},
350+
});
351+
expect(verifyRes.data.createdWith).toEqual(originalCreatedWith);
352+
});
353+
354+
it('should allow master key to update expiresAt on a session', async () => {
355+
const user = await Parse.User.signUp('sessionupdateuser3', 'password');
356+
const sessionToken = user.getSessionToken();
357+
358+
// Get the session objectId
359+
const sessionRes = await request({
360+
method: 'GET',
361+
url: 'http://localhost:8378/1/sessions/me',
362+
headers: {
363+
'X-Parse-Application-Id': 'test',
364+
'X-Parse-REST-API-Key': 'rest',
365+
'X-Parse-Session-Token': sessionToken,
366+
},
367+
});
368+
const sessionId = sessionRes.data.objectId;
369+
const farFuture = '2099-12-31T23:59:59.000Z';
370+
371+
// Master key should be able to update expiresAt
372+
await request({
373+
method: 'PUT',
374+
url: `http://localhost:8378/1/sessions/${sessionId}`,
375+
headers: {
376+
'X-Parse-Application-Id': 'test',
377+
'X-Parse-Master-Key': 'test',
378+
'Content-Type': 'application/json',
379+
},
380+
body: {
381+
expiresAt: { __type: 'Date', iso: farFuture },
382+
},
383+
});
384+
385+
// Verify expiresAt was changed
386+
const verifyRes = await request({
387+
method: 'GET',
388+
url: `http://localhost:8378/1/sessions/${sessionId}`,
389+
headers: {
390+
'X-Parse-Application-Id': 'test',
391+
'X-Parse-Master-Key': 'test',
392+
},
393+
});
394+
expect(verifyRes.data.expiresAt.iso).toBe(farFuture);
395+
});
396+
260397
describe('PUT /sessions/me', () => {
261398
it('should return error with invalid session token', async () => {
262399
const response = await request({

src/RestWrite.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,6 +1180,10 @@ RestWrite.prototype.handleSession = function () {
11801180
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
11811181
} else if (this.data.sessionToken) {
11821182
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
1183+
} else if (this.data.expiresAt && !this.auth.isMaster && !this.auth.isMaintenance) {
1184+
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
1185+
} else if (this.data.createdWith && !this.auth.isMaster && !this.auth.isMaintenance) {
1186+
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
11831187
}
11841188
if (!this.auth.isMaster) {
11851189
this.query = {

0 commit comments

Comments
 (0)