VYPR
Critical severityNVD Advisory· Published Jun 28, 2023· Updated Nov 27, 2024

Parse Server vulnerable to remote code execution via MongoDB BSON parser through prototype pollution

CVE-2023-36475

Description

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.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
parse-servernpm
< 5.5.25.5.2
parse-servernpm
>= 6.0.0, < 6.2.16.2.1

Affected products

1

Patches

2
5fad2928fb8e

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

6 files changed · +101 33
  • .eslintrc.json+3 0 modified
    @@ -24,5 +24,8 @@
             "space-infix-ops": "error",
             "no-useless-escape": "off",
             "require-atomic-updates": "off"
    +    },
    +    "globals": {
    +        "Parse": true
         }
     }
    
  • spec/vulnerabilities.spec.js+65 0 modified
    @@ -138,6 +138,71 @@ describe('Vulnerabilities', () => {
           );
         });
     
    +    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"}.'
    +        )
    +      );
    +    });
    +
         it('denies creating a hook with polluted data', async () => {
           const express = require('express');
           const bodyParser = require('body-parser');
    
  • src/Controllers/DatabaseController.js+10 0 modified
    @@ -467,6 +467,11 @@ class DatabaseController {
         validateOnly: boolean = false,
         validSchemaController: SchemaController.SchemaController
       ): Promise<any> {
    +    try {
    +      Utils.checkProhibitedKeywords(this.options, update);
    +    } catch (error) {
    +      return Promise.reject(new Parse.Error(Parse.Error.INVALID_KEY_NAME, error));
    +    }
         const originalQuery = query;
         const originalUpdate = update;
         // Make a copy of the object, so we don't mutate the incoming data.
    @@ -797,6 +802,11 @@ class DatabaseController {
         validateOnly: boolean = false,
         validSchemaController: SchemaController.SchemaController
       ): Promise<any> {
    +    try {
    +      Utils.checkProhibitedKeywords(this.options, object);
    +    } catch (error) {
    +      return Promise.reject(new Parse.Error(Parse.Error.INVALID_KEY_NAME, error));
    +    }
         // Make a copy of the object, so we don't mutate the incoming data.
         const originalObject = object;
         object = transformObjectACL(object);
    
  • src/RestWrite.js+5 18 modified
    @@ -65,8 +65,6 @@ function RestWrite(config, auth, className, query, data, originalData, clientSDK
         }
       }
     
    -  this.checkProhibitedKeywords(data);
    -
       // When the operation is complete, this.response may have several
       // fields.
       // response: the actual data to be returned
    @@ -288,7 +286,11 @@ RestWrite.prototype.runBeforeSaveTrigger = function () {
               delete this.data.objectId;
             }
           }
    -      this.checkProhibitedKeywords(this.data);
    +      try {
    +        Utils.checkProhibitedKeywords(this.config, this.data);
    +      } catch (error) {
    +        throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, error);
    +      }
         });
     };
     
    @@ -1730,20 +1732,5 @@ RestWrite.prototype._updateResponseWithData = function (response, data) {
       return response;
     };
     
    -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)}.`
    -        );
    -      }
    -    }
    -  }
    -};
    -
     export default RestWrite;
     module.exports = RestWrite;
    
  • src/Routers/FilesRouter.js+6 15 modified
    @@ -173,22 +173,13 @@ export class FilesRouter {
         const base64 = req.body.toString('base64');
         const file = new Parse.File(filename, { base64 }, contentType);
         const { metadata = {}, tags = {} } = req.fileData || {};
    -    if (req.config && req.config.requestKeywordDenylist) {
    +    try {
           // 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;
    -        }
    -      }
    +      Utils.checkProhibitedKeywords(config, metadata);
    +      Utils.checkProhibitedKeywords(config, tags);
    +    } catch (error) {
    +      next(new Parse.Error(Parse.Error.INVALID_KEY_NAME, error));
    +      return;
         }
         file.setTags(tags);
         file.setMetadata(metadata);
    
  • src/Utils.js+12 0 modified
    @@ -358,6 +358,18 @@ class Utils {
         }
         return false;
       }
    +
    +  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)}.`;
    +        }
    +      }
    +    }
    +  }
     }
     
     module.exports = Utils;
    
3dd99dd80e27

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)

6 files changed · +101 34
  • .eslintrc.json+3 0 modified
    @@ -25,5 +25,8 @@
             "space-infix-ops": "error",
             "no-useless-escape": "off",
             "require-atomic-updates": "off"
    +    },
    +    "globals": {
    +        "Parse": true
         }
     }
    
  • spec/vulnerabilities.spec.js+65 0 modified
    @@ -138,6 +138,71 @@ describe('Vulnerabilities', () => {
           );
         });
     
    +    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"}.'
    +        )
    +      );
    +    });
    +
         it('denies creating a hook with polluted data', async () => {
           const express = require('express');
           const bodyParser = require('body-parser');
    
  • src/Controllers/DatabaseController.js+10 0 modified
    @@ -475,6 +475,11 @@ class DatabaseController {
         validateOnly: boolean = false,
         validSchemaController: SchemaController.SchemaController
       ): Promise<any> {
    +    try {
    +      Utils.checkProhibitedKeywords(this.options, update);
    +    } catch (error) {
    +      return Promise.reject(new Parse.Error(Parse.Error.INVALID_KEY_NAME, error));
    +    }
         const originalQuery = query;
         const originalUpdate = update;
         // Make a copy of the object, so we don't mutate the incoming data.
    @@ -805,6 +810,11 @@ class DatabaseController {
         validateOnly: boolean = false,
         validSchemaController: SchemaController.SchemaController
       ): Promise<any> {
    +    try {
    +      Utils.checkProhibitedKeywords(this.options, object);
    +    } catch (error) {
    +      return Promise.reject(new Parse.Error(Parse.Error.INVALID_KEY_NAME, error));
    +    }
         // Make a copy of the object, so we don't mutate the incoming data.
         const originalObject = object;
         object = transformObjectACL(object);
    
  • src/RestWrite.js+5 18 modified
    @@ -64,8 +64,6 @@ function RestWrite(config, auth, className, query, data, originalData, clientSDK
         }
       }
     
    -  this.checkProhibitedKeywords(data);
    -
       // When the operation is complete, this.response may have several
       // fields.
       // response: the actual data to be returned
    @@ -298,7 +296,11 @@ RestWrite.prototype.runBeforeSaveTrigger = function () {
               delete this.data.objectId;
             }
           }
    -      this.checkProhibitedKeywords(this.data);
    +      try {
    +        Utils.checkProhibitedKeywords(this.config, this.data);
    +      } catch (error) {
    +        throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, error);
    +      }
         });
     };
     
    @@ -1756,20 +1758,5 @@ RestWrite.prototype._updateResponseWithData = function (response, data) {
       return response;
     };
     
    -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)}.`
    -        );
    -      }
    -    }
    -  }
    -};
    -
     export default RestWrite;
     module.exports = RestWrite;
    
  • src/Routers/FilesRouter.js+6 16 modified
    @@ -175,22 +175,12 @@ export class FilesRouter {
         const base64 = req.body.toString('base64');
         const file = new Parse.File(filename, { base64 }, contentType);
         const { metadata = {}, tags = {} } = req.fileData || {};
    -    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;
    -        }
    -      }
    +    try {
    +      Utils.checkProhibitedKeywords(config, metadata);
    +      Utils.checkProhibitedKeywords(config, tags);
    +    } catch (error) {
    +      next(new Parse.Error(Parse.Error.INVALID_KEY_NAME, error));
    +      return;
         }
         file.setTags(tags);
         file.setMetadata(metadata);
    
  • src/Utils.js+12 0 modified
    @@ -358,6 +358,18 @@ class Utils {
         }
         return false;
       }
    +
    +  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)}.`;
    +        }
    +      }
    +    }
    +  }
     }
     
     module.exports = Utils;
    

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

9

News mentions

0

No linked articles in our index yet.