From b279d73991e24c14440245d0001b21033e3082d9 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Thu, 24 Mar 2022 00:27:14 +0100 Subject: [PATCH 1/2] fix string comparison --- src/Controllers/DatabaseController.js | 2 +- src/Utils.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index a436842447..7c5153f839 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -1763,7 +1763,7 @@ class DatabaseController { if (this.options && this.options.requestKeywordDenylist) { // Scan request data for denied keywords for (const keyword of this.options.requestKeywordDenylist) { - const isMatch = (a, b) => (typeof a === 'string' && new RegExp(a).test(b)) || a === b; + const isMatch = (a, b) => (typeof a === 'string' && new RegExp(b).test(a)) || a === b; if (isMatch(firstKey, keyword.key)) { throw new Parse.Error( Parse.Error.INVALID_KEY_NAME, diff --git a/src/Utils.js b/src/Utils.js index 399939a152..6d66cde567 100644 --- a/src/Utils.js +++ b/src/Utils.js @@ -341,7 +341,7 @@ class Utils { * @returns {Boolean} True if a match was found, false otherwise. */ static objectContainsKeyValue(obj, key, value) { - const isMatch = (a, b) => (typeof a === 'string' && new RegExp(a).test(b)) || a === b; + const isMatch = (a, b) => (typeof a === 'string' && new RegExp(b).test(a)) || a === b; const isKeyMatch = k => isMatch(key, k); const isValueMatch = v => isMatch(value, v); for (const [k, v] of Object.entries(obj)) { From 640f9262d1136ad8040ccc750f54f97b66e84661 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Thu, 24 Mar 2022 02:17:59 +0100 Subject: [PATCH 2/2] fix and optimize keyword detection --- spec/vulnerabilities.spec.js | 14 ++++++++++++++ src/Controllers/DatabaseController.js | 5 +++-- src/Utils.js | 4 ++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/spec/vulnerabilities.spec.js b/spec/vulnerabilities.spec.js index 1255d64398..02a4ff5433 100644 --- a/spec/vulnerabilities.spec.js +++ b/spec/vulnerabilities.spec.js @@ -280,4 +280,18 @@ describe('Vulnerabilities', () => { expect(text.error).toBe('Prohibited keyword in request data: {"value":"aValue[123]*"}.'); }); }); + + describe('Ignore non-matches', () => { + it('ignores write request that contains only fraction of denied keyword', async () => { + await reconfigureServer({ + requestKeywordDenylist: [{ key: 'abc' }], + }); + // Initially saving an object executes the keyword detection in RestWrite.js + const obj = new TestObject({ a: { b: { c: 0 } } }); + await expectAsync(obj.save()).toBeResolved(); + // Modifying a nested key executes the keyword detection in DatabaseController.js + obj.increment('a.b.c'); + await expectAsync(obj.save()).toBeResolved(); + }); + }); }); diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 7c5153f839..3e69b1f5eb 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -11,6 +11,7 @@ import intersect from 'intersect'; // @flow-disable-next import deepcopy from 'deepcopy'; import logger from '../logger'; +import Utils from '../Utils'; import * as SchemaController from './SchemaController'; import { StorageAdapter } from '../Adapters/Storage/StorageAdapter'; import MongoStorageAdapter from '../Adapters/Storage/Mongo/MongoStorageAdapter'; @@ -1763,8 +1764,8 @@ class DatabaseController { if (this.options && this.options.requestKeywordDenylist) { // Scan request data for denied keywords for (const keyword of this.options.requestKeywordDenylist) { - const isMatch = (a, b) => (typeof a === 'string' && new RegExp(b).test(a)) || a === b; - if (isMatch(firstKey, keyword.key)) { + const match = Utils.objectContainsKeyValue({ firstKey: undefined }, keyword.key, undefined); + if (match) { throw new Parse.Error( Parse.Error.INVALID_KEY_NAME, `Prohibited keyword in request data: ${JSON.stringify(keyword)}.` diff --git a/src/Utils.js b/src/Utils.js index 6d66cde567..d5a255a5ca 100644 --- a/src/Utils.js +++ b/src/Utils.js @@ -342,8 +342,8 @@ class Utils { */ static objectContainsKeyValue(obj, key, value) { const isMatch = (a, b) => (typeof a === 'string' && new RegExp(b).test(a)) || a === b; - const isKeyMatch = k => isMatch(key, k); - const isValueMatch = v => isMatch(value, v); + const isKeyMatch = k => isMatch(k, key); + const isValueMatch = v => isMatch(v, value); for (const [k, v] of Object.entries(obj)) { if (key !== undefined && value === undefined && isKeyMatch(k)) { return true;