CVE-2021-39187 (JavaScript, published 2021-09-02T16:15Z)
Description: value: Parse Server is an open source backend that can be deployed to any infrastructure that can run Node.js. Prior to version 4.10.3, Parse Server crashes when if a query request contains an invalid value for the `explain` option. This is due to a bug in the MongoDB Node.js driver which throws an exception that Parse Server cannot catch. There is a patch for this issue in version 4.10.3. No workarounds aside from upgrading are known to exist.
Severity: MEDIUM (CVSS v3 base score: 7.5)
Weakness: CWE-755 \u2014 Improper Handling of Exceptional Conditions
The product does not handle or incorrectly handles an exceptional condition.
Repository: https://github.com/parse-community/parse-server
Files changed:
CHANGELOG.md
spec/ParseQuery.spec.js
src/Adapters/Storage/Mongo/MongoStorageAdapter.js
src/RestQuery.js
Commit message: Merge pull request from GHSA-xqp8-w826-hh6x

* Added a test case that triggers the query parameter crash

* rest.js: validate the explain parameter to keep the nodejs driver from throwing an uncatchable exception and crashing the server (see https://jira.mongodb.org/browse/NODE-3463)
RestQuery.js: Check whether explain mode is enabled not by "!== true", but by the "!" operator. explain can have string values.
Added tests that validate correct behaviour on different explain values

* Refactor the new tests

* Simplify the new tests
Also do a sanity check on the explain results

* Test refactor

* Exclude queryPlannerExtended as it is not supported by the testing environment
  Simplifies the tests

* Restrict the changes to mongodb
  Moved the verification of the explain value from rest.js to MongoStorageAdapter.js
  Also restricted the relevant unit tests to mongodb

* Added changelog entry

* reformat changelog entry

* Update CHANGELOG.md

Co-authored-by: Kartal Kaan Bozdoğan <kartalkaanbozdogan@gmail.com>
Co-authored-by: Manuel <5673677+mtrezza@users.noreply.github.com>

Vulnerable code:
| [**4.10.2 (latest release)**](#4102) |
[Full Changelog](https://github.com/parse-community/parse-server/compare/4.10.2...master)
      if (this.className === '_User' && findOptions.explain !== true) {
      if (i == (keyPath.length - 1)) {

Fixed code:
| [**4.10.3 (latest release)**](#4103) |
| [4.10.2](#4102)                      |
[Full Changelog](https://github.com/parse-community/parse-server/compare/4.10.3...master)


## 4.10.3
[Full Changelog](https://github.com/parse-community/parse-server/compare/4.10.2...4.10.3)

### Security Fixes
- Validate `explain` query parameter to avoid a server crash due to MongoDB bug [NODE-3463](https://jira.mongodb.org/browse/NODE-3463) (Kartal Kaan Bozdogan) [GHSA-xqp8-w826-hh6x](https://github.com/parse-community/parse-server/security/advisories/GHSA-xqp8-w826-hh6x)

  it_only_db('mongo')('gracefully handles invalid explain values', async () => {
    // Note that anything that is not truthy (like 0) does not cause an exception, as they get swallowed up by ClassesRouter::optionsFromBody
    const values = [1, 'yolo', { a: 1 }, [1, 2, 3]];
    for (const value of values) {
      try {
        await request({
          method: 'GET',
          url: `http://localhost:8378/1/classes/_User?explain=${value}`,
          json: true,
          headers: masterKeyHeaders,
        });
        fail('request did not throw');
      } catch (e) {
        // Expect that Parse Server did not crash
        expect(e.code).not.toEqual('ECONNRESET');
        // Expect that Parse Server validates the explain value and does not crash;
        // see https://jira.mongodb.org/browse/NODE-3463
        equal(e.data.code, Parse.Error.INVALID_QUERY);
        equal(e.data.error, 'Invalid value for explain');
      }
      // get queries (of the form '/classes/:className/:objectId' cannot have the explain key, see ClassesRouter.js)
      // so it is enough that we test find queries
    }
  });

  it_only_db('mongo')('supports valid explain values', async () => {
    const values = [
      false,
      true,
      'queryPlanner',
      'executionStats',
      'allPlansExecution',
      // 'queryPlannerExtended' is excluded as it only applies to MongoDB Data Lake which is currently not available in our CI environment
    ];
    for (const value of values) {
      const response = await request({
        method: 'GET',
        url: `http://localhost:8378/1/classes/_User?explain=${value}`,
        json: true,
        headers: masterKeyHeaders,
      });
      expect(response.status).toBe(200);
      if (value) {
        expect(response.data.results.ok).toBe(1);
      }
    }
  });

function validateExplainValue(explain) {
  if (explain) {
    // The list of allowed explain values is from node-mongodb-native/lib/explain.js
    const explainAllowedValues = [
      'queryPlanner',
      'queryPlannerExtended',
      'executionStats',
      'allPlansExecution',
      false,
      true,
    ];
    if (!explainAllowedValues.includes(explain)) {
      throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Invalid value for explain');
    }
  }
}

    validateExplainValue(explain);
    validateExplainValue(explain);
      if (this.className === '_User' && !findOptions.explain) {
      if (i == keyPath.length - 1) {