CVE-2023-36475 (JavaScript, published 2023-06-28T23:15Z)
Description: value: Parse Server is an open source backend that can be deployed to any infrastructure that can run Node.js. Prior to versions 5.5.2 and 6.2.1, an attacker can use a prototype pollution sink to trigger a remote code execution through the MongoDB BSON parser. A patch is available in versions 5.5.2 and 6.2.1.
Severity: nan (CVSS v3 base score: 9.8)
Weakness: CWE-1321 \u2014 Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')
The product receives input from an upstream component that specifies attributes that are to be initialized or updated in an object, but it does not properly control modifications of attributes of the object prototype.
Repository: https://github.com/parse-community/parse-server
Files changed:
.eslintrc.json
spec/vulnerabilities.spec.js
src/Controllers/DatabaseController.js
src/RestWrite.js
src/Routers/FilesRouter.js
src/Utils.js
Commit message: fix: Remote code execution via MongoDB BSON parser through prototype pollution; fixes security vulnerability [GHSA-462x-c3jw-7vr6](https://github.com/parse-community/parse-server/security/advisories/GHSA-462x-c3jw-7vr6) (#8674)



Vulnerable code:
  this.checkProhibitedKeywords(data);

      this.checkProhibitedKeywords(this.data);
RestWrite.prototype.checkProhibitedKeywords = function (data) {
  if (this.config.requestKeywordDenylist) {
    // Scan request data for denied keywords
    for (const keyword of this.config.requestKeywordDenylist) {
      const match = Utils.objectContainsKeyValue(data, keyword.key, keyword.value);
      if (match) {
        throw new Parse.Error(
          Parse.Error.INVALID_KEY_NAME,
          `Prohibited keyword in request data: ${JSON.stringify(keyword)}.`
        );
      }
    }
  }
};

    if (req.config && req.config.requestKeywordDenylist) {
      // Scan request data for denied keywords
      for (const keyword of req.config.requestKeywordDenylist) {
        const match =
          Utils.objectContainsKeyValue(metadata, keyword.key, keyword.value) ||
          Utils.objectContainsKeyValue(tags, keyword.key, keyword.value);
        if (match) {
          next(
            new Parse.Error(
              Parse.Error.INVALID_KEY_NAME,
              `Prohibited keyword in request data: ${JSON.stringify(keyword)}.`
            )
          );
          return;
        }
      }

Fixed code:
    },
    "globals": {
        "Parse": true
    it('denies creating global config with polluted data', async () => {
      const headers = {
        'Content-Type': 'application/json',
        'X-Parse-Application-Id': 'test',
        'X-Parse-Master-Key': 'test',
      };
      const params = {
        method: 'PUT',
        url: 'http://localhost:8378/1/config',
        json: true,
        body: {
          params: {
            welcomeMesssage: 'Welcome to Parse',
            foo: { _bsontype: 'Code', code: 'shell' },
          },
        },
        headers,
      };
      const response = await request(params).catch(e => e);
      expect(response.status).toBe(400);
      const text = JSON.parse(response.text);
      expect(text.code).toBe(Parse.Error.INVALID_KEY_NAME);
      expect(text.error).toBe(
        'Prohibited keyword in request data: {"key":"_bsontype","value":"Code"}.'
      );
    });

    it('denies direct database write wih prohibited keys', async () => {
      const Config = require('../lib/Config');
      const config = Config.get(Parse.applicationId);
      const user = {
        objectId: '1234567890',
        username: 'hello',
        password: 'pass',
        _session_token: 'abc',
        foo: { _bsontype: 'Code', code: 'shell' },
      };
      await expectAsync(config.database.create('_User', user)).toBeRejectedWith(
        new Parse.Error(
          Parse.Error.INVALID_KEY_NAME,
          'Prohibited keyword in request data: {"key":"_bsontype","value":"Code"}.'
        )
      );
    });

    it('denies direct database update wih prohibited keys', async () => {
      const Config = require('../lib/Config');
      const config = Config.get(Parse.applicationId);
      const user = {
        objectId: '1234567890',
        username: 'hello',
        password: 'pass',
        _session_token: 'abc',
        foo: { _bsontype: 'Code', code: 'shell' },
      };
      await expectAsync(
        config.database.update('_User', { _id: user.objectId }, user)
      ).toBeRejectedWith(
        new Parse.Error(
          Parse.Error.INVALID_KEY_NAME,
          'Prohibited keyword in request data: {"key":"_bsontype","value":"Code"}.'
        )
      );
    });

    try {
      Utils.checkProhibitedKeywords(this.options, update);
    } catch (error) {
      return Promise.reject(new Parse.Error(Parse.Error.INVALID_KEY_NAME, error));
    }
    try {
      Utils.checkProhibitedKeywords(this.options, object);
    } catch (error) {
      return Promise.reject(new Parse.Error(Parse.Error.INVALID_KEY_NAME, error));
    }
      try {
        Utils.checkProhibitedKeywords(this.config, this.data);
      } catch (error) {
        throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, error);
      }
    try {
      Utils.checkProhibitedKeywords(config, metadata);
      Utils.checkProhibitedKeywords(config, tags);
    } catch (error) {
      next(new Parse.Error(Parse.Error.INVALID_KEY_NAME, error));
      return;

  static checkProhibitedKeywords(config, data) {
    if (config?.requestKeywordDenylist) {
      // Scan request data for denied keywords
      for (const keyword of config.requestKeywordDenylist) {
        const match = Utils.objectContainsKeyValue(data, keyword.key, keyword.value);
        if (match) {
          throw `Prohibited keyword in request data: ${JSON.stringify(keyword)}.`;
        }
      }
    }
  }