Skip to content

Commit fbac847

Browse files
authored
fix: Denial of service via unindexed database query for unconfigured auth providers ([GHSA-g4cf-xj29-wqqr](GHSA-g4cf-xj29-wqqr)) (#10270)
1 parent 48cc262 commit fbac847

File tree

2 files changed

+71
-1
lines changed

2 files changed

+71
-1
lines changed

spec/vulnerabilities.spec.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4142,3 +4142,67 @@ describe('(GHSA-6qh5-m6g3-xhq6) LiveQuery query depth DoS via deeply nested subs
41424142
expect(subscription).toBeDefined();
41434143
});
41444144
});
4145+
4146+
describe('(GHSA-g4cf-xj29-wqqr) DoS via unindexed database query for unconfigured auth providers', () => {
4147+
it('should not query database for unconfigured auth provider on signup', async () => {
4148+
const databaseAdapter = Config.get(Parse.applicationId).database.adapter;
4149+
const spy = spyOn(databaseAdapter, 'find').and.callThrough();
4150+
await expectAsync(
4151+
new Parse.User().save({ authData: { nonExistentProvider: { id: 'test123' } } })
4152+
).toBeRejectedWith(
4153+
new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE, 'This authentication method is unsupported.')
4154+
);
4155+
const authDataQueries = spy.calls.all().filter(call => {
4156+
const query = call.args[2];
4157+
return query?.$or?.some(q => q['authData.nonExistentProvider.id']);
4158+
});
4159+
expect(authDataQueries.length).toBe(0);
4160+
});
4161+
4162+
it('should not query database for unconfigured auth provider on challenge', async () => {
4163+
const databaseAdapter = Config.get(Parse.applicationId).database.adapter;
4164+
const spy = spyOn(databaseAdapter, 'find').and.callThrough();
4165+
await expectAsync(
4166+
request({
4167+
method: 'POST',
4168+
url: Parse.serverURL + '/challenge',
4169+
headers: {
4170+
'X-Parse-Application-Id': Parse.applicationId,
4171+
'X-Parse-REST-API-Key': 'rest',
4172+
'Content-Type': 'application/json',
4173+
},
4174+
body: JSON.stringify({
4175+
authData: { nonExistentProvider: { id: 'test123' } },
4176+
challengeData: { nonExistentProvider: { token: 'abc' } },
4177+
}),
4178+
})
4179+
).toBeRejected();
4180+
const authDataQueries = spy.calls.all().filter(call => {
4181+
const query = call.args[2];
4182+
return query?.$or?.some(q => q['authData.nonExistentProvider.id']);
4183+
});
4184+
expect(authDataQueries.length).toBe(0);
4185+
});
4186+
4187+
it('should still query database for configured auth provider', async () => {
4188+
await reconfigureServer({
4189+
auth: {
4190+
myConfiguredProvider: {
4191+
module: {
4192+
validateAppId: () => Promise.resolve(),
4193+
validateAuthData: () => Promise.resolve(),
4194+
},
4195+
},
4196+
},
4197+
});
4198+
const databaseAdapter = Config.get(Parse.applicationId).database.adapter;
4199+
const spy = spyOn(databaseAdapter, 'find').and.callThrough();
4200+
const user = new Parse.User();
4201+
await user.save({ authData: { myConfiguredProvider: { id: 'validId', token: 'validToken' } } });
4202+
const authDataQueries = spy.calls.all().filter(call => {
4203+
const query = call.args[2];
4204+
return query?.$or?.some(q => q['authData.myConfiguredProvider.id']);
4205+
});
4206+
expect(authDataQueries.length).toBeGreaterThan(0);
4207+
});
4208+
});

src/Auth.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,13 @@ const findUsersWithAuthData = async (config, authData, beforeFind, currentUserAu
448448
const isUnchanged = storedProviderData && incomingKeys.length > 0 &&
449449
!incomingKeys.some(key => !isDeepStrictEqual(providerAuthData[key], storedProviderData[key]));
450450

451-
const adapter = config.authDataManager.getValidatorForProvider(provider)?.adapter;
451+
const validatorConfig = config.authDataManager.getValidatorForProvider(provider);
452+
// Skip database query for unconfigured providers to avoid unindexed collection scans;
453+
// the provider will be rejected later in handleAuthDataValidation with UNSUPPORTED_SERVICE
454+
if (!validatorConfig?.validator) {
455+
return null;
456+
}
457+
const adapter = validatorConfig.adapter;
452458
if (beforeFind && typeof adapter?.beforeFind === 'function' && !isUnchanged) {
453459
await adapter.beforeFind(providerAuthData);
454460
}

0 commit comments

Comments
 (0)