Skip to content

Commit

Permalink
fix: Custom object ID allows to acquire role privileges ([GHSA-8xq9-g…
Browse files Browse the repository at this point in the history
  • Loading branch information
mtrezza authored Oct 3, 2024
1 parent b86906f commit 13ee52f
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 0 deletions.
45 changes: 45 additions & 0 deletions spec/vulnerabilities.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,51 @@
const request = require('../lib/request');

describe('Vulnerabilities', () => {
describe('(GHSA-8xq9-g7ch-35hg) Custom object ID allows to acquire role privilege', () => {
beforeAll(async () => {
await reconfigureServer({ allowCustomObjectId: true });
Parse.allowCustomObjectId = true;
});

afterAll(async () => {
await reconfigureServer({ allowCustomObjectId: false });
Parse.allowCustomObjectId = false;
});

it('denies user creation with poisoned object ID', async () => {
await expectAsync(
new Parse.User({ id: 'role:a', username: 'a', password: '123' }).save()
).toBeRejectedWith(new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Invalid object ID.'));
});

describe('existing sessions for users with poisoned object ID', () => {
/** @type {Parse.User} */
let poisonedUser;
/** @type {Parse.User} */
let innocentUser;

beforeAll(async () => {
const parseServer = await global.reconfigureServer();
const databaseController = parseServer.config.databaseController;
[poisonedUser, innocentUser] = await Promise.all(
['role:abc', 'abc'].map(async id => {
// Create the users directly on the db to bypass the user creation check
await databaseController.create('_User', { objectId: id });
// Use the master key to create a session for them to bypass the session check
return Parse.User.loginAs(id);
})
);
});

it('refuses session token of user with poisoned object ID', async () => {
await expectAsync(
new Parse.Query(Parse.User).find({ sessionToken: poisonedUser.getSessionToken() })
).toBeRejectedWith(new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Invalid object ID.'));
await new Parse.Query(Parse.User).find({ sessionToken: innocentUser.getSessionToken() });
});
});
});

describe('Object prototype pollution', () => {
it('denies object prototype to be polluted with keyword "constructor"', async () => {
const headers = {
Expand Down
5 changes: 5 additions & 0 deletions src/Auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,11 @@ const getAuthForSessionToken = async function ({
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token is expired.');
}
const obj = session.user;

if (typeof obj['objectId'] === 'string' && obj['objectId'].startsWith('role:')) {
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Invalid object ID.');
}

delete obj.password;
obj['className'] = '_User';
obj['sessionToken'] = sessionToken;
Expand Down
7 changes: 7 additions & 0 deletions src/Routers/ClassesRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,13 @@ export class ClassesRouter extends PromiseRouter {
}

handleCreate(req) {
if (
this.className(req) === '_User' &&
typeof req.body?.objectId === 'string' &&
req.body.objectId.startsWith('role:')
) {
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Invalid object ID.');
}
return rest.create(
req.config,
req.auth,
Expand Down

0 comments on commit 13ee52f

Please sign in to comment.