From 4e284cf491cb2a20926c26e6f219a07466bf58d3 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Mon, 16 Mar 2026 18:44:54 +0000 Subject: [PATCH] fix: Block dot-notation updates to authData sub-fields and harden login provider checks --- spec/vulnerabilities.spec.js | 50 +++++++++++++++++++++++++++ src/Auth.js | 13 ++++--- src/Controllers/DatabaseController.js | 2 +- 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/spec/vulnerabilities.spec.js b/spec/vulnerabilities.spec.js index a8567a3718..3f97d247e3 100644 --- a/spec/vulnerabilities.spec.js +++ b/spec/vulnerabilities.spec.js @@ -3010,6 +3010,56 @@ describe('(GHSA-fjxm-vhvc-gcmj) LiveQuery Operator Type Confusion', () => { }); }); + describe('authData dot-notation injection and login crash', () => { + it('rejects dotted update key that targets authData sub-field', async () => { + const user = new Parse.User(); + user.setUsername('dotuser'); + user.setPassword('pass1234'); + await user.signUp(); + + const res = await request({ + method: 'PUT', + url: `http://localhost:8378/1/users/${user.id}`, + headers: { + 'Content-Type': 'application/json', + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Session-Token': user.getSessionToken(), + }, + body: JSON.stringify({ 'authData.anonymous".id': 'injected' }), + }).catch(e => e); + expect(res.status).toBe(400); + }); + + it('login does not crash when stored authData has unknown provider', async () => { + const user = new Parse.User(); + user.setUsername('dotuser2'); + user.setPassword('pass1234'); + await user.signUp(); + await Parse.User.logOut(); + + // Inject unknown provider directly in database to simulate corrupted data + const config = Config.get('test'); + await config.database.update( + '_User', + { objectId: user.id }, + { authData: { unknown_provider: { id: 'bad' } } } + ); + + // Login should not crash with 500 + const login = await request({ + method: 'GET', + url: `http://localhost:8378/1/login?username=dotuser2&password=pass1234`, + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + }, + }).catch(e => e); + expect(login.status).toBe(200); + expect(login.data.sessionToken).toBeDefined(); + }); + }); + describe('(GHSA-r3xq-68wh-gwvh) Password reset single-use token bypass via concurrent requests', () => { let sendPasswordResetEmail; diff --git a/src/Auth.js b/src/Auth.js index 7dd8660ad7..9610cbe019 100644 --- a/src/Auth.js +++ b/src/Auth.js @@ -523,10 +523,15 @@ const checkIfUserHasProvidedConfiguredProvidersForLogin = ( userAuthData = {}, config ) => { - const savedUserProviders = Object.keys(userAuthData).map(provider => ({ - name: provider, - adapter: config.authDataManager.getValidatorForProvider(provider).adapter, - })); + const savedUserProviders = Object.keys(userAuthData) + .map(provider => { + const validator = config.authDataManager.getValidatorForProvider(provider); + if (!validator || !validator.adapter) { + return null; + } + return { name: provider, adapter: validator.adapter }; + }) + .filter(Boolean); const hasProvidedASoloProvider = savedUserProviders.some( provider => diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 59e3b089be..f8ba7543e7 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -603,7 +603,7 @@ class DatabaseController { }) .then(schema => { Object.keys(update).forEach(fieldName => { - if (fieldName.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) { + if (fieldName.match(/^authData\./)) { throw new Parse.Error( Parse.Error.INVALID_KEY_NAME, `Invalid field name for update: ${fieldName}`