From 35f7c7cb3cec848b5899b7ba097eb34c14c3d2b2 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Wed, 2 Oct 2024 08:47:19 +0100 Subject: [PATCH] [ML] Various fixes for possible prototype pollution vulnerabilities (#194529) Fixes potential prototype pollution vulnerability in `setNestedProperty` function. Fixes incomplete string escaping issue in ML's saved object service. (cherry picked from commit d1f24b050b53cc7b13fbc47b6de3c5f69606e88e) --- .../ml/nested_property/src/set_nested_property.test.ts | 10 ++++++++++ .../ml/nested_property/src/set_nested_property.ts | 6 ++++++ x-pack/plugins/ml/server/saved_objects/service.ts | 4 ++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/x-pack/packages/ml/nested_property/src/set_nested_property.test.ts b/x-pack/packages/ml/nested_property/src/set_nested_property.test.ts index 43996f603ffa0..963b0611197db 100644 --- a/x-pack/packages/ml/nested_property/src/set_nested_property.test.ts +++ b/x-pack/packages/ml/nested_property/src/set_nested_property.test.ts @@ -68,5 +68,15 @@ describe('object_utils', () => { const test11 = setNestedProperty(getFalseyObject(), 'the.other_nested.value', 'update'); expect(test11.the.other_nested.value).toBe('update'); + + expect(() => { + setNestedProperty(getTestObj(), 'the.__proto__', 'update'); + }).toThrow('Invalid accessor'); + expect(() => { + setNestedProperty(getTestObj(), 'the.prototype', 'update'); + }).toThrow('Invalid accessor'); + expect(() => { + setNestedProperty(getTestObj(), 'the.constructor', 'update'); + }).toThrow('Invalid accessor'); }); }); diff --git a/x-pack/packages/ml/nested_property/src/set_nested_property.ts b/x-pack/packages/ml/nested_property/src/set_nested_property.ts index b963983cb16ab..6c692cb3a0a08 100644 --- a/x-pack/packages/ml/nested_property/src/set_nested_property.ts +++ b/x-pack/packages/ml/nested_property/src/set_nested_property.ts @@ -5,9 +5,15 @@ * 2.0. */ +const INVALID_ACCESSORS = ['__proto__', 'prototype', 'constructor']; + export const setNestedProperty = (obj: Record, accessor: string, value: any) => { let ref = obj; const accessors = accessor.split('.'); + if (accessors.some((a) => INVALID_ACCESSORS.includes(a))) { + throw new Error('Invalid accessor'); + } + const len = accessors.length; for (let i = 0; i < len - 1; i++) { const attribute = accessors[i]; diff --git a/x-pack/plugins/ml/server/saved_objects/service.ts b/x-pack/plugins/ml/server/saved_objects/service.ts index e6c32f2bc6531..3e0f81a8ba13b 100644 --- a/x-pack/plugins/ml/server/saved_objects/service.ts +++ b/x-pack/plugins/ml/server/saved_objects/service.ts @@ -328,7 +328,7 @@ export function mlSavedObjectServiceFactory( if (id.match('\\*') === null) { return jobIds.includes(id); } - const regex = new RegExp(id.replace('*', '.*')); + const regex = new RegExp(id.replaceAll('*', '.*')); return jobIds.some((jId) => typeof jId === 'string' && regex.exec(jId)); }); } @@ -640,7 +640,7 @@ export function mlSavedObjectServiceFactory( if (id.match('\\*') === null) { return modelIds.includes(id); } - const regex = new RegExp(id.replace('*', '.*')); + const regex = new RegExp(id.replaceAll('*', '.*')); return modelIds.some((jId) => typeof jId === 'string' && regex.exec(jId)); }); }