mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
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:
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user