fix: Endpoint /sessions/me bypasses _Session protectedFields ([GHSA-g4v2-qx3q-4p64](https://github.com/parse-community/parse-server/security/advisories/GHSA-g4v2-qx3q-4p64)) (#10406)

This commit is contained in:
Manuel
2026-04-06 17:46:32 +01:00
committed by GitHub
parent 9168e69cb7
commit d5075758f6
2 changed files with 105 additions and 20 deletions
+61
View File
@@ -5685,4 +5685,65 @@ describe('Vulnerabilities', () => {
expect(response.status).toBe(403);
});
});
describe('(GHSA-g4v2-qx3q-4p64) /sessions/me bypasses _Session protectedFields', () => {
const headers = {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'Content-Type': 'application/json',
};
it('should not return protected fields on GET /sessions/me', async () => {
await reconfigureServer({
protectedFields: {
_Session: { '*': ['createdWith'] },
},
});
const user = new Parse.User();
user.setUsername('session-pf-user');
user.setPassword('password123');
user.set('email', 'session-pf@example.com');
await user.signUp();
const sessionToken = user.getSessionToken();
// Normal GET /sessions should strip createdWith
const sessionsResponse = await request({
method: 'GET',
url: 'http://localhost:8378/1/sessions',
headers: { ...headers, 'X-Parse-Session-Token': sessionToken },
});
expect(sessionsResponse.data.results[0].createdWith).toBeUndefined();
// GET /sessions/me should also strip createdWith
const meResponse = await request({
method: 'GET',
url: 'http://localhost:8378/1/sessions/me',
headers: { ...headers, 'X-Parse-Session-Token': sessionToken },
});
expect(meResponse.data.createdWith).toBeUndefined();
});
it('should return non-protected fields on GET /sessions/me', async () => {
await reconfigureServer({
protectedFields: {
_Session: { '*': ['createdWith'] },
},
});
const user = new Parse.User();
user.setUsername('session-pf-user2');
user.setPassword('password123');
user.set('email', 'session-pf2@example.com');
await user.signUp();
const sessionToken = user.getSessionToken();
const meResponse = await request({
method: 'GET',
url: 'http://localhost:8378/1/sessions/me',
headers: { ...headers, 'X-Parse-Session-Token': sessionToken },
});
expect(meResponse.data.sessionToken).toBe(sessionToken);
expect(meResponse.data.objectId).toBeDefined();
expect(meResponse.data.user).toBeDefined();
});
});
});
+44 -20
View File
@@ -9,29 +9,53 @@ export class SessionsRouter extends ClassesRouter {
return '_Session';
}
handleMe(req) {
// TODO: Verify correct behavior
async handleMe(req) {
if (!req.info || !req.info.sessionToken) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token required.');
}
return rest
.find(
req.config,
Auth.master(req.config),
'_Session',
{ sessionToken: req.info.sessionToken },
undefined,
req.info.clientSDK,
req.info.context
)
.then(response => {
if (!response.results || response.results.length == 0) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token not found.');
}
return {
response: response.results[0],
};
});
const sessionToken = req.info.sessionToken;
// Query with master key to validate the session token and get the session objectId
const sessionResponse = await rest.find(
req.config,
Auth.master(req.config),
'_Session',
{ sessionToken },
{},
req.info.clientSDK,
req.info.context
);
if (
!sessionResponse.results ||
sessionResponse.results.length == 0 ||
!sessionResponse.results[0].user
) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token not found.');
}
const sessionObjectId = sessionResponse.results[0].objectId;
const userId = sessionResponse.results[0].user.objectId;
// Re-fetch the session with the caller's auth context so that
// protectedFields and CLP apply correctly
const userAuth = new Auth.Auth({
config: req.config,
isMaster: false,
user: Parse.Object.fromJSON({ className: '_User', objectId: userId }),
installationId: req.info.installationId,
});
const response = await rest.get(
req.config,
userAuth,
'_Session',
sessionObjectId,
{},
req.info.clientSDK,
req.info.context
);
if (!response.results || response.results.length == 0) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token not found.');
}
return {
response: response.results[0],
};
}
handleUpdateToRevocableSession(req) {