VYPR
High severityNVD Advisory· Published Oct 4, 2024· Updated Oct 4, 2024

Parse Server's custom object ID allows to acquire role privileges

CVE-2024-47183

Description

Parse Server is an open source backend that can be deployed to any infrastructure that can run Node.js. If the Parse Server option allowCustomObjectId: true is set, an attacker that is allowed to create a new user can set a custom object ID for that new user that exploits the vulnerability and acquires privileges of a specific role. This vulnerability is fixed in 6.5.9 and 7.3.0.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
parse-servernpm
< 6.5.96.5.9
parse-servernpm
>= 7.0.0, < 7.3.07.3.0

Affected products

1

Patches

2
1bfbccf9ee7e

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)

5 files changed · +75 92
  • package.json+1 1 modified
    @@ -48,7 +48,7 @@
         "mongodb": "4.10.0",
         "mustache": "4.2.0",
         "otpauth": "9.2.2",
    -    "parse": "4.1.0",
    +    "parse": "4.2.0",
         "path-to-regexp": "6.2.1",
         "pg-monitor": "2.0.0",
         "pg-promise": "11.5.4",
    
  • package-lock.json+17 91 modified
    @@ -18,11 +18,11 @@
             "@parse/fs-files-adapter": "2.0.1",
             "@parse/push-adapter": "5.1.1",
             "bcryptjs": "2.4.3",
    -        "body-parser": "^1.20.3",
    +        "body-parser": "1.20.3",
             "commander": "10.0.1",
             "cors": "2.8.5",
             "deepcopy": "2.1.0",
    -        "express": "^4.21.0",
    +        "express": "4.21.0",
             "express-rate-limit": "6.7.0",
             "follow-redirects": "1.15.6",
             "graphql": "16.8.1",
    @@ -39,7 +39,7 @@
             "mongodb": "4.10.0",
             "mustache": "4.2.0",
             "otpauth": "9.2.2",
    -        "parse": "4.1.0",
    +        "parse": "4.2.0",
             "path-to-regexp": "6.2.1",
             "pg-monitor": "2.0.0",
             "pg-promise": "11.5.4",
    @@ -3099,53 +3099,6 @@
             "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": {
           "version": "1.1.2",
           "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
    @@ -16946,15 +16899,15 @@
           }
         },
         "node_modules/parse": {
    -      "version": "4.1.0",
    -      "resolved": "https://registry.npmjs.org/parse/-/parse-4.1.0.tgz",
    -      "integrity": "sha512-s0Ti+nWrKWj9DlFcmkEE05fGwa/K5ycZSdqCz01F8YL7Hevqv4WLXAmYGOwzq5UJSZ005seKgb20KwVwLdy/Zg==",
    +      "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.12.0",
    +        "ws": "8.13.0",
             "xmlhttprequest": "1.8.0"
           },
           "engines": {
    @@ -17000,9 +16953,9 @@
           }
         },
         "node_modules/parse/node_modules/ws": {
    -      "version": "8.12.0",
    -      "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz",
    -      "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==",
    +      "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"
           },
    @@ -23970,33 +23923,6 @@
             "firebase-admin": "12.0.0",
             "npmlog": "7.0.1",
             "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": {
    @@ -34573,16 +34499,16 @@
           }
         },
         "parse": {
    -      "version": "4.1.0",
    -      "resolved": "https://registry.npmjs.org/parse/-/parse-4.1.0.tgz",
    -      "integrity": "sha512-s0Ti+nWrKWj9DlFcmkEE05fGwa/K5ycZSdqCz01F8YL7Hevqv4WLXAmYGOwzq5UJSZ005seKgb20KwVwLdy/Zg==",
    +      "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.12.0",
    +        "ws": "8.13.0",
             "xmlhttprequest": "1.8.0"
           },
           "dependencies": {
    @@ -34592,9 +34518,9 @@
               "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
             },
             "ws": {
    -          "version": "8.12.0",
    -          "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz",
    -          "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==",
    +          "version": "8.13.0",
    +          "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
    +          "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
               "requires": {}
             }
           }
    
  • spec/vulnerabilities.spec.js+45 0 modified
    @@ -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 = {
    
  • src/Auth.js+5 0 modified
    @@ -173,6 +173,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;
    
  • src/Routers/ClassesRouter.js+7 0 modified
    @@ -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,
    
13ee52f0d19e

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)) (#9317)

3 files changed · +57 0
  • spec/vulnerabilities.spec.js+45 0 modified
    @@ -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 = {
    
  • src/Auth.js+5 0 modified
    @@ -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;
    
  • src/Routers/ClassesRouter.js+7 0 modified
    @@ -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,
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.