mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
fix: Denial of service via unindexed database query for unconfigured auth providers ([GHSA-g4cf-xj29-wqqr](https://github.com/parse-community/parse-server/security/advisories/GHSA-g4cf-xj29-wqqr)) (#10271)
This commit is contained in:
@@ -3716,3 +3716,67 @@ describe('(GHSA-6qh5-m6g3-xhq6) LiveQuery query depth DoS via deeply nested subs
|
||||
expect(subscription).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('(GHSA-g4cf-xj29-wqqr) DoS via unindexed database query for unconfigured auth providers', () => {
|
||||
it('should not query database for unconfigured auth provider on signup', async () => {
|
||||
const databaseAdapter = Config.get(Parse.applicationId).database.adapter;
|
||||
const spy = spyOn(databaseAdapter, 'find').and.callThrough();
|
||||
await expectAsync(
|
||||
new Parse.User().save({ authData: { nonExistentProvider: { id: 'test123' } } })
|
||||
).toBeRejectedWith(
|
||||
new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE, 'This authentication method is unsupported.')
|
||||
);
|
||||
const authDataQueries = spy.calls.all().filter(call => {
|
||||
const query = call.args[2];
|
||||
return query?.$or?.some(q => q['authData.nonExistentProvider.id']);
|
||||
});
|
||||
expect(authDataQueries.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should not query database for unconfigured auth provider on challenge', async () => {
|
||||
const databaseAdapter = Config.get(Parse.applicationId).database.adapter;
|
||||
const spy = spyOn(databaseAdapter, 'find').and.callThrough();
|
||||
await expectAsync(
|
||||
request({
|
||||
method: 'POST',
|
||||
url: Parse.serverURL + '/challenge',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
authData: { nonExistentProvider: { id: 'test123' } },
|
||||
challengeData: { nonExistentProvider: { token: 'abc' } },
|
||||
}),
|
||||
})
|
||||
).toBeRejected();
|
||||
const authDataQueries = spy.calls.all().filter(call => {
|
||||
const query = call.args[2];
|
||||
return query?.$or?.some(q => q['authData.nonExistentProvider.id']);
|
||||
});
|
||||
expect(authDataQueries.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should still query database for configured auth provider', async () => {
|
||||
await reconfigureServer({
|
||||
auth: {
|
||||
myConfiguredProvider: {
|
||||
module: {
|
||||
validateAppId: () => Promise.resolve(),
|
||||
validateAuthData: () => Promise.resolve(),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const databaseAdapter = Config.get(Parse.applicationId).database.adapter;
|
||||
const spy = spyOn(databaseAdapter, 'find').and.callThrough();
|
||||
const user = new Parse.User();
|
||||
await user.save({ authData: { myConfiguredProvider: { id: 'validId', token: 'validToken' } } });
|
||||
const authDataQueries = spy.calls.all().filter(call => {
|
||||
const query = call.args[2];
|
||||
return query?.$or?.some(q => q['authData.myConfiguredProvider.id']);
|
||||
});
|
||||
expect(authDataQueries.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
+7
-1
@@ -424,7 +424,13 @@ const findUsersWithAuthData = async (config, authData, beforeFind) => {
|
||||
providers.map(async provider => {
|
||||
const providerAuthData = authData[provider];
|
||||
|
||||
const adapter = config.authDataManager.getValidatorForProvider(provider)?.adapter;
|
||||
const validatorConfig = config.authDataManager.getValidatorForProvider(provider);
|
||||
// Skip database query for unconfigured providers to avoid unindexed collection scans;
|
||||
// the provider will be rejected later in handleAuthDataValidation with UNSUPPORTED_SERVICE
|
||||
if (!validatorConfig?.validator) {
|
||||
return null;
|
||||
}
|
||||
const adapter = validatorConfig.adapter;
|
||||
if (beforeFind && typeof adapter?.beforeFind === 'function') {
|
||||
await adapter.beforeFind(providerAuthData);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user