Skip to content

Commit d507575

Browse files
authored
fix: Endpoint /sessions/me bypasses _Session protectedFields ([GHSA-g4v2-qx3q-4p64](GHSA-g4v2-qx3q-4p64)) (#10406)
1 parent 9168e69 commit d507575

File tree

2 files changed

+105
-20
lines changed

2 files changed

+105
-20
lines changed

spec/vulnerabilities.spec.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5685,4 +5685,65 @@ describe('Vulnerabilities', () => {
56855685
expect(response.status).toBe(403);
56865686
});
56875687
});
5688+
5689+
describe('(GHSA-g4v2-qx3q-4p64) /sessions/me bypasses _Session protectedFields', () => {
5690+
const headers = {
5691+
'X-Parse-Application-Id': 'test',
5692+
'X-Parse-REST-API-Key': 'rest',
5693+
'Content-Type': 'application/json',
5694+
};
5695+
5696+
it('should not return protected fields on GET /sessions/me', async () => {
5697+
await reconfigureServer({
5698+
protectedFields: {
5699+
_Session: { '*': ['createdWith'] },
5700+
},
5701+
});
5702+
const user = new Parse.User();
5703+
user.setUsername('session-pf-user');
5704+
user.setPassword('password123');
5705+
user.set('email', 'session-pf@example.com');
5706+
await user.signUp();
5707+
const sessionToken = user.getSessionToken();
5708+
5709+
// Normal GET /sessions should strip createdWith
5710+
const sessionsResponse = await request({
5711+
method: 'GET',
5712+
url: 'http://localhost:8378/1/sessions',
5713+
headers: { ...headers, 'X-Parse-Session-Token': sessionToken },
5714+
});
5715+
expect(sessionsResponse.data.results[0].createdWith).toBeUndefined();
5716+
5717+
// GET /sessions/me should also strip createdWith
5718+
const meResponse = await request({
5719+
method: 'GET',
5720+
url: 'http://localhost:8378/1/sessions/me',
5721+
headers: { ...headers, 'X-Parse-Session-Token': sessionToken },
5722+
});
5723+
expect(meResponse.data.createdWith).toBeUndefined();
5724+
});
5725+
5726+
it('should return non-protected fields on GET /sessions/me', async () => {
5727+
await reconfigureServer({
5728+
protectedFields: {
5729+
_Session: { '*': ['createdWith'] },
5730+
},
5731+
});
5732+
const user = new Parse.User();
5733+
user.setUsername('session-pf-user2');
5734+
user.setPassword('password123');
5735+
user.set('email', 'session-pf2@example.com');
5736+
await user.signUp();
5737+
const sessionToken = user.getSessionToken();
5738+
5739+
const meResponse = await request({
5740+
method: 'GET',
5741+
url: 'http://localhost:8378/1/sessions/me',
5742+
headers: { ...headers, 'X-Parse-Session-Token': sessionToken },
5743+
});
5744+
expect(meResponse.data.sessionToken).toBe(sessionToken);
5745+
expect(meResponse.data.objectId).toBeDefined();
5746+
expect(meResponse.data.user).toBeDefined();
5747+
});
5748+
});
56885749
});

src/Routers/SessionsRouter.js

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,53 @@ export class SessionsRouter extends ClassesRouter {
99
return '_Session';
1010
}
1111

12-
handleMe(req) {
13-
// TODO: Verify correct behavior
12+
async handleMe(req) {
1413
if (!req.info || !req.info.sessionToken) {
1514
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token required.');
1615
}
17-
return rest
18-
.find(
19-
req.config,
20-
Auth.master(req.config),
21-
'_Session',
22-
{ sessionToken: req.info.sessionToken },
23-
undefined,
24-
req.info.clientSDK,
25-
req.info.context
26-
)
27-
.then(response => {
28-
if (!response.results || response.results.length == 0) {
29-
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token not found.');
30-
}
31-
return {
32-
response: response.results[0],
33-
};
34-
});
16+
const sessionToken = req.info.sessionToken;
17+
// Query with master key to validate the session token and get the session objectId
18+
const sessionResponse = await rest.find(
19+
req.config,
20+
Auth.master(req.config),
21+
'_Session',
22+
{ sessionToken },
23+
{},
24+
req.info.clientSDK,
25+
req.info.context
26+
);
27+
if (
28+
!sessionResponse.results ||
29+
sessionResponse.results.length == 0 ||
30+
!sessionResponse.results[0].user
31+
) {
32+
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token not found.');
33+
}
34+
const sessionObjectId = sessionResponse.results[0].objectId;
35+
const userId = sessionResponse.results[0].user.objectId;
36+
// Re-fetch the session with the caller's auth context so that
37+
// protectedFields and CLP apply correctly
38+
const userAuth = new Auth.Auth({
39+
config: req.config,
40+
isMaster: false,
41+
user: Parse.Object.fromJSON({ className: '_User', objectId: userId }),
42+
installationId: req.info.installationId,
43+
});
44+
const response = await rest.get(
45+
req.config,
46+
userAuth,
47+
'_Session',
48+
sessionObjectId,
49+
{},
50+
req.info.clientSDK,
51+
req.info.context
52+
);
53+
if (!response.results || response.results.length == 0) {
54+
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token not found.');
55+
}
56+
return {
57+
response: response.results[0],
58+
};
3559
}
3660

3761
handleUpdateToRevocableSession(req) {

0 commit comments

Comments
 (0)