mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
fix: Session update endpoint allows overwriting server-generated session fields ([GHSA-jc39-686j-wp6q](https://github.com/parse-community/parse-server/security/advisories/GHSA-jc39-686j-wp6q)) (#10264)
This commit is contained in:
@@ -256,4 +256,141 @@ describe('Parse.Session', () => {
|
||||
expect(newSession.createdWith.action).toBe('create');
|
||||
expect(newSession.createdWith.authProvider).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should reject expiresAt when updating a session via PUT', async () => {
|
||||
const user = await Parse.User.signUp('sessionupdateuser1', 'password');
|
||||
const sessionToken = user.getSessionToken();
|
||||
|
||||
// Get the session objectId
|
||||
const sessionRes = await request({
|
||||
method: 'GET',
|
||||
url: 'http://localhost:8378/1/sessions/me',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'X-Parse-Session-Token': sessionToken,
|
||||
},
|
||||
});
|
||||
const sessionId = sessionRes.data.objectId;
|
||||
const originalExpiresAt = sessionRes.data.expiresAt;
|
||||
|
||||
// Attempt to overwrite expiresAt via PUT
|
||||
const updateRes = await request({
|
||||
method: 'PUT',
|
||||
url: `http://localhost:8378/1/sessions/${sessionId}`,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'X-Parse-Session-Token': sessionToken,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: {
|
||||
expiresAt: { __type: 'Date', iso: '2099-12-31T23:59:59.000Z' },
|
||||
},
|
||||
}).catch(e => e);
|
||||
|
||||
expect(updateRes.data.code).toBe(Parse.Error.INVALID_KEY_NAME);
|
||||
|
||||
// Verify expiresAt was not changed
|
||||
const verifyRes = await request({
|
||||
method: 'GET',
|
||||
url: 'http://localhost:8378/1/sessions/me',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'X-Parse-Session-Token': sessionToken,
|
||||
},
|
||||
});
|
||||
expect(verifyRes.data.expiresAt).toEqual(originalExpiresAt);
|
||||
});
|
||||
|
||||
it('should reject createdWith when updating a session via PUT', async () => {
|
||||
const user = await Parse.User.signUp('sessionupdateuser2', 'password');
|
||||
const sessionToken = user.getSessionToken();
|
||||
|
||||
// Get the session objectId
|
||||
const sessionRes = await request({
|
||||
method: 'GET',
|
||||
url: 'http://localhost:8378/1/sessions/me',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'X-Parse-Session-Token': sessionToken,
|
||||
},
|
||||
});
|
||||
const sessionId = sessionRes.data.objectId;
|
||||
const originalCreatedWith = sessionRes.data.createdWith;
|
||||
|
||||
// Attempt to overwrite createdWith via PUT
|
||||
const updateRes = await request({
|
||||
method: 'PUT',
|
||||
url: `http://localhost:8378/1/sessions/${sessionId}`,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'X-Parse-Session-Token': sessionToken,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: {
|
||||
createdWith: { action: 'attacker', authProvider: 'evil' },
|
||||
},
|
||||
}).catch(e => e);
|
||||
|
||||
expect(updateRes.data.code).toBe(Parse.Error.INVALID_KEY_NAME);
|
||||
|
||||
// Verify createdWith was not changed
|
||||
const verifyRes = await request({
|
||||
method: 'GET',
|
||||
url: 'http://localhost:8378/1/sessions/me',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'X-Parse-Session-Token': sessionToken,
|
||||
},
|
||||
});
|
||||
expect(verifyRes.data.createdWith).toEqual(originalCreatedWith);
|
||||
});
|
||||
|
||||
it('should allow master key to update expiresAt on a session', async () => {
|
||||
const user = await Parse.User.signUp('sessionupdateuser3', 'password');
|
||||
const sessionToken = user.getSessionToken();
|
||||
|
||||
// Get the session objectId
|
||||
const sessionRes = await request({
|
||||
method: 'GET',
|
||||
url: 'http://localhost:8378/1/sessions/me',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'X-Parse-Session-Token': sessionToken,
|
||||
},
|
||||
});
|
||||
const sessionId = sessionRes.data.objectId;
|
||||
const farFuture = '2099-12-31T23:59:59.000Z';
|
||||
|
||||
// Master key should be able to update expiresAt
|
||||
await request({
|
||||
method: 'PUT',
|
||||
url: `http://localhost:8378/1/sessions/${sessionId}`,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key': 'test',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: {
|
||||
expiresAt: { __type: 'Date', iso: farFuture },
|
||||
},
|
||||
});
|
||||
|
||||
// Verify expiresAt was changed
|
||||
const verifyRes = await request({
|
||||
method: 'GET',
|
||||
url: `http://localhost:8378/1/sessions/${sessionId}`,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key': 'test',
|
||||
},
|
||||
});
|
||||
expect(verifyRes.data.expiresAt.iso).toBe(farFuture);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1147,6 +1147,10 @@ RestWrite.prototype.handleSession = function () {
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
|
||||
} else if (this.data.sessionToken) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
|
||||
} else if (this.data.expiresAt && !this.auth.isMaster && !this.auth.isMaintenance) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
|
||||
} else if (this.data.createdWith && !this.auth.isMaster && !this.auth.isMaintenance) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
|
||||
}
|
||||
if (!this.auth.isMaster) {
|
||||
this.query = {
|
||||
|
||||
Reference in New Issue
Block a user