fix: Custom object ID allows to acquire role privileges ([GHSA-8xq9-g7ch-35hg](https://github.com/parse-community/parse-server/security/advisories/GHSA-8xq9-g7ch-35hg)) (#9318)
This commit is contained in:
108
package-lock.json
generated
108
package-lock.json
generated
@@ -18,11 +18,11 @@
|
|||||||
"@parse/fs-files-adapter": "2.0.1",
|
"@parse/fs-files-adapter": "2.0.1",
|
||||||
"@parse/push-adapter": "5.1.1",
|
"@parse/push-adapter": "5.1.1",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"body-parser": "^1.20.3",
|
"body-parser": "1.20.3",
|
||||||
"commander": "10.0.1",
|
"commander": "10.0.1",
|
||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
"deepcopy": "2.1.0",
|
"deepcopy": "2.1.0",
|
||||||
"express": "^4.21.0",
|
"express": "4.21.0",
|
||||||
"express-rate-limit": "6.7.0",
|
"express-rate-limit": "6.7.0",
|
||||||
"follow-redirects": "1.15.6",
|
"follow-redirects": "1.15.6",
|
||||||
"graphql": "16.8.1",
|
"graphql": "16.8.1",
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
"mongodb": "4.10.0",
|
"mongodb": "4.10.0",
|
||||||
"mustache": "4.2.0",
|
"mustache": "4.2.0",
|
||||||
"otpauth": "9.2.2",
|
"otpauth": "9.2.2",
|
||||||
"parse": "4.1.0",
|
"parse": "4.2.0",
|
||||||
"path-to-regexp": "6.2.1",
|
"path-to-regexp": "6.2.1",
|
||||||
"pg-monitor": "2.0.0",
|
"pg-monitor": "2.0.0",
|
||||||
"pg-promise": "11.5.4",
|
"pg-promise": "11.5.4",
|
||||||
@@ -3099,53 +3099,6 @@
|
|||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@parse/push-adapter/node_modules/parse": {
|
|
||||||
"version": "4.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/parse/-/parse-4.2.0.tgz",
|
|
||||||
"integrity": "sha512-K8bWs0wM2qRhkSr6N16j8OvsF6Uallrynqng9e+tzR3RdKuB09vaJh48qrf9MbiJ1Ya4JZI7AfEHYF+ywEKs7Q==",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime-corejs3": "7.21.0",
|
|
||||||
"idb-keyval": "6.2.0",
|
|
||||||
"react-native-crypto-js": "1.0.0",
|
|
||||||
"uuid": "9.0.0",
|
|
||||||
"ws": "8.13.0",
|
|
||||||
"xmlhttprequest": "1.8.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.21.0 <17 || >=18 <20"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"crypto-js": "4.1.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@parse/push-adapter/node_modules/uuid": {
|
|
||||||
"version": "9.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
|
||||||
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
|
|
||||||
"bin": {
|
|
||||||
"uuid": "dist/bin/uuid"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@parse/push-adapter/node_modules/ws": {
|
|
||||||
"version": "8.13.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
|
|
||||||
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"bufferutil": "^4.0.1",
|
|
||||||
"utf-8-validate": ">=5.0.2"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"bufferutil": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"utf-8-validate": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@protobufjs/aspromise": {
|
"node_modules/@protobufjs/aspromise": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||||
@@ -16946,15 +16899,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/parse": {
|
"node_modules/parse": {
|
||||||
"version": "4.1.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/parse/-/parse-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/parse/-/parse-4.2.0.tgz",
|
||||||
"integrity": "sha512-s0Ti+nWrKWj9DlFcmkEE05fGwa/K5ycZSdqCz01F8YL7Hevqv4WLXAmYGOwzq5UJSZ005seKgb20KwVwLdy/Zg==",
|
"integrity": "sha512-K8bWs0wM2qRhkSr6N16j8OvsF6Uallrynqng9e+tzR3RdKuB09vaJh48qrf9MbiJ1Ya4JZI7AfEHYF+ywEKs7Q==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime-corejs3": "7.21.0",
|
"@babel/runtime-corejs3": "7.21.0",
|
||||||
"idb-keyval": "6.2.0",
|
"idb-keyval": "6.2.0",
|
||||||
"react-native-crypto-js": "1.0.0",
|
"react-native-crypto-js": "1.0.0",
|
||||||
"uuid": "9.0.0",
|
"uuid": "9.0.0",
|
||||||
"ws": "8.12.0",
|
"ws": "8.13.0",
|
||||||
"xmlhttprequest": "1.8.0"
|
"xmlhttprequest": "1.8.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -17000,9 +16953,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/parse/node_modules/ws": {
|
"node_modules/parse/node_modules/ws": {
|
||||||
"version": "8.12.0",
|
"version": "8.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
|
||||||
"integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==",
|
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
},
|
},
|
||||||
@@ -23970,33 +23923,6 @@
|
|||||||
"firebase-admin": "12.0.0",
|
"firebase-admin": "12.0.0",
|
||||||
"npmlog": "7.0.1",
|
"npmlog": "7.0.1",
|
||||||
"parse": "4.2.0"
|
"parse": "4.2.0"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"parse": {
|
|
||||||
"version": "4.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/parse/-/parse-4.2.0.tgz",
|
|
||||||
"integrity": "sha512-K8bWs0wM2qRhkSr6N16j8OvsF6Uallrynqng9e+tzR3RdKuB09vaJh48qrf9MbiJ1Ya4JZI7AfEHYF+ywEKs7Q==",
|
|
||||||
"requires": {
|
|
||||||
"@babel/runtime-corejs3": "7.21.0",
|
|
||||||
"crypto-js": "4.1.1",
|
|
||||||
"idb-keyval": "6.2.0",
|
|
||||||
"react-native-crypto-js": "1.0.0",
|
|
||||||
"uuid": "9.0.0",
|
|
||||||
"ws": "8.13.0",
|
|
||||||
"xmlhttprequest": "1.8.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"uuid": {
|
|
||||||
"version": "9.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
|
||||||
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
|
|
||||||
},
|
|
||||||
"ws": {
|
|
||||||
"version": "8.13.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
|
|
||||||
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
|
|
||||||
"requires": {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@protobufjs/aspromise": {
|
"@protobufjs/aspromise": {
|
||||||
@@ -34573,16 +34499,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"parse": {
|
"parse": {
|
||||||
"version": "4.1.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/parse/-/parse-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/parse/-/parse-4.2.0.tgz",
|
||||||
"integrity": "sha512-s0Ti+nWrKWj9DlFcmkEE05fGwa/K5ycZSdqCz01F8YL7Hevqv4WLXAmYGOwzq5UJSZ005seKgb20KwVwLdy/Zg==",
|
"integrity": "sha512-K8bWs0wM2qRhkSr6N16j8OvsF6Uallrynqng9e+tzR3RdKuB09vaJh48qrf9MbiJ1Ya4JZI7AfEHYF+ywEKs7Q==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime-corejs3": "7.21.0",
|
"@babel/runtime-corejs3": "7.21.0",
|
||||||
"crypto-js": "4.1.1",
|
"crypto-js": "4.1.1",
|
||||||
"idb-keyval": "6.2.0",
|
"idb-keyval": "6.2.0",
|
||||||
"react-native-crypto-js": "1.0.0",
|
"react-native-crypto-js": "1.0.0",
|
||||||
"uuid": "9.0.0",
|
"uuid": "9.0.0",
|
||||||
"ws": "8.12.0",
|
"ws": "8.13.0",
|
||||||
"xmlhttprequest": "1.8.0"
|
"xmlhttprequest": "1.8.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -34592,9 +34518,9 @@
|
|||||||
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
|
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
|
||||||
},
|
},
|
||||||
"ws": {
|
"ws": {
|
||||||
"version": "8.12.0",
|
"version": "8.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
|
||||||
"integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==",
|
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
"mongodb": "4.10.0",
|
"mongodb": "4.10.0",
|
||||||
"mustache": "4.2.0",
|
"mustache": "4.2.0",
|
||||||
"otpauth": "9.2.2",
|
"otpauth": "9.2.2",
|
||||||
"parse": "4.1.0",
|
"parse": "4.2.0",
|
||||||
"path-to-regexp": "6.2.1",
|
"path-to-regexp": "6.2.1",
|
||||||
"pg-monitor": "2.0.0",
|
"pg-monitor": "2.0.0",
|
||||||
"pg-promise": "11.5.4",
|
"pg-promise": "11.5.4",
|
||||||
|
|||||||
@@ -1,6 +1,51 @@
|
|||||||
const request = require('../lib/request');
|
const request = require('../lib/request');
|
||||||
|
|
||||||
describe('Vulnerabilities', () => {
|
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', () => {
|
describe('Object prototype pollution', () => {
|
||||||
it('denies object prototype to be polluted with keyword "constructor"', async () => {
|
it('denies object prototype to be polluted with keyword "constructor"', async () => {
|
||||||
const headers = {
|
const headers = {
|
||||||
|
|||||||
@@ -173,6 +173,11 @@ const getAuthForSessionToken = async function ({
|
|||||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token is expired.');
|
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token is expired.');
|
||||||
}
|
}
|
||||||
const obj = session.user;
|
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;
|
delete obj.password;
|
||||||
obj['className'] = '_User';
|
obj['className'] = '_User';
|
||||||
obj['sessionToken'] = sessionToken;
|
obj['sessionToken'] = sessionToken;
|
||||||
|
|||||||
@@ -106,6 +106,13 @@ export class ClassesRouter extends PromiseRouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleCreate(req) {
|
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(
|
return rest.create(
|
||||||
req.config,
|
req.config,
|
||||||
req.auth,
|
req.auth,
|
||||||
|
|||||||
Reference in New Issue
Block a user