From 8c8ec715739e0f851338cfed794409ebac66c51b Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Tue, 20 Sep 2022 23:05:44 +0200 Subject: [PATCH] fix: authentication adapter app ID validation may be circumvented; this fixes a vulnerability that affects configurations which allow users to authenticate using the Parse Server authentication adapter for *Facebook* or *Spotify* and where the server-side authentication adapter configuration `appIds` is set as a string (e.g. `abc`) instead of an array of strings (e.g. `["abc"]`) ([GHSA-r657-33vp-gp22](https://github.com/parse-community/parse-server/security/advisories/GHSA-r657-33vp-gp22)) [skip release] (#8187) --- spec/AuthenticationAdapters.spec.js | 23 +++++++++++++++++++++++ src/Adapters/Auth/facebook.js | 19 ++++++++++--------- src/Adapters/Auth/spotify.js | 15 ++++++++------- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/spec/AuthenticationAdapters.spec.js b/spec/AuthenticationAdapters.spec.js index 044255c0e5..6f02b3c8c5 100644 --- a/spec/AuthenticationAdapters.spec.js +++ b/spec/AuthenticationAdapters.spec.js @@ -419,6 +419,29 @@ describe('AuthenticationProviders', function () { expect(providerOptions).toEqual(options.facebook); }); + it('should throw error when Facebook request appId is wrong data type', async () => { + const httpsRequest = require('../lib/Adapters/Auth/httpsRequest'); + spyOn(httpsRequest, 'get').and.callFake(() => { + return Promise.resolve({ id: 'a' }); + }); + const options = { + facebook: { + appIds: 'abcd', + appSecret: 'secret_sauce', + }, + }; + const authData = { + access_token: 'badtoken', + }; + const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( + 'facebook', + options + ); + await expectAsync(adapter.validateAppId(appIds, authData, providerOptions)).toBeRejectedWith( + new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'appIds must be an array.') + ); + }); + it('should handle Facebook appSecret for validating appIds', async () => { const httpsRequest = require('../lib/Adapters/Auth/httpsRequest'); spyOn(httpsRequest, 'get').and.callFake(() => { diff --git a/src/Adapters/Auth/facebook.js b/src/Adapters/Auth/facebook.js index 95fba0b3e7..e937bb1bb5 100644 --- a/src/Adapters/Auth/facebook.js +++ b/src/Adapters/Auth/facebook.js @@ -32,22 +32,23 @@ function validateGraphToken(authData, options) { }); } -function validateGraphAppId(appIds, authData, options) { +async function validateGraphAppId(appIds, authData, options) { var access_token = authData.access_token; if (process.env.TESTING && access_token === 'test') { - return Promise.resolve(); + return; + } + if (!Array.isArray(appIds)) { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'appIds must be an array.'); } if (!appIds.length) { throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Facebook auth is not configured.'); } - return graphRequest( - 'app?access_token=' + access_token + getAppSecretPath(authData, options) - ).then(data => { - if (data && appIds.indexOf(data.id) != -1) { - return; - } + const data = await graphRequest( + `app?access_token=${access_token}${getAppSecretPath(authData, options)}` + ); + if (!data || !appIds.includes(data.id)) { throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Facebook auth is invalid for this user.'); - }); + } } const getFacebookKeyByKeyId = async (keyId, cacheMaxEntries, cacheMaxAge) => { diff --git a/src/Adapters/Auth/spotify.js b/src/Adapters/Auth/spotify.js index 69e6cbae2d..604868d078 100644 --- a/src/Adapters/Auth/spotify.js +++ b/src/Adapters/Auth/spotify.js @@ -13,17 +13,18 @@ function validateAuthData(authData) { } // Returns a promise that fulfills if this app id is valid. -function validateAppId(appIds, authData) { - var access_token = authData.access_token; +async function validateAppId(appIds, authData) { + const access_token = authData.access_token; + if (!Array.isArray(appIds)) { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'appIds must be an array.'); + } if (!appIds.length) { throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Spotify auth is not configured.'); } - return request('me', access_token).then(data => { - if (data && appIds.indexOf(data.id) != -1) { - return; - } + const data = await request('me', access_token); + if (!data || !appIds.includes(data.id)) { throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Spotify auth is invalid for this user.'); - }); + } } // A promisey wrapper for Spotify API requests.