fix: Session field immutability bypass via falsy-value guard ([GHSA-f6j3-w9v3-cq22](https://github.com/parse-community/parse-server/security/advisories/GHSA-f6j3-w9v3-cq22)) (#10348)

This commit is contained in:
Manuel
2026-03-29 04:56:09 +01:00
committed by GitHub
parent fbd138cc26
commit ebccd7fe27
2 changed files with 156 additions and 4 deletions
+152
View File
@@ -393,4 +393,156 @@ describe('Parse.Session', () => {
});
expect(verifyRes.data.expiresAt.iso).toBe(farFuture);
});
it('should reject null expiresAt when updating a session via PUT', async () => {
const user = await Parse.User.signUp('sessionupdatenull1', 'password');
const sessionToken = user.getSessionToken();
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;
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: null,
},
}).catch(e => e);
expect(updateRes.data.code).toBe(Parse.Error.INVALID_KEY_NAME);
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 null createdWith when updating a session via PUT', async () => {
const user = await Parse.User.signUp('sessionupdatenull2', 'password');
const sessionToken = user.getSessionToken();
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;
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: null,
},
}).catch(e => e);
expect(updateRes.data.code).toBe(Parse.Error.INVALID_KEY_NAME);
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 reject null installationId when updating a session via PUT', async () => {
const user = await Parse.User.signUp('sessionupdatenull3', 'password');
const sessionToken = user.getSessionToken();
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 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: {
installationId: null,
},
}).catch(e => e);
expect(updateRes.data.code).toBe(Parse.Error.INVALID_KEY_NAME);
});
it('should reject null sessionToken when updating a session via PUT', async () => {
const user = await Parse.User.signUp('sessionupdatenull4', 'password');
const sessionToken = user.getSessionToken();
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 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: {
sessionToken: null,
},
}).catch(e => e);
expect(updateRes.data.code).toBe(Parse.Error.INVALID_KEY_NAME);
});
});
+4 -4
View File
@@ -1174,13 +1174,13 @@ RestWrite.prototype.handleSession = function () {
if (this.query) {
if (this.data.user && !this.auth.isMaster && this.data.user.objectId != this.auth.user.id) {
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
} else if (this.data.installationId) {
} else if ('installationId' in this.data) {
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
} else if (this.data.sessionToken) {
} else if ('sessionToken' in this.data) {
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
} else if (this.data.expiresAt && !this.auth.isMaster && !this.auth.isMaintenance) {
} else if ('expiresAt' in this.data && !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) {
} else if ('createdWith' in this.data && !this.auth.isMaster && !this.auth.isMaintenance) {
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
}
if (!this.auth.isMaster) {