Skip to content

Commit 0c7290c

Browse files
committed
fix: Validate authData input in dot-notation updates, login provider checks, and challenge endpoint
1 parent 9667fd7 commit 0c7290c

4 files changed

Lines changed: 107 additions & 7 deletions

File tree

spec/vulnerabilities.spec.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3010,6 +3010,92 @@ describe('(GHSA-fjxm-vhvc-gcmj) LiveQuery Operator Type Confusion', () => {
30103010
});
30113011
});
30123012

3013+
describe('(GHSA-g55g-hrp7-h6vq) Dotted authData provider injection', () => {
3014+
it('rejects dotted update key that targets authData sub-field', async () => {
3015+
const user = new Parse.User();
3016+
user.setUsername('dotuser');
3017+
user.setPassword('pass1234');
3018+
await user.signUp();
3019+
3020+
const res = await request({
3021+
method: 'PUT',
3022+
url: `http://localhost:8378/1/users/${user.id}`,
3023+
headers: {
3024+
'Content-Type': 'application/json',
3025+
'X-Parse-Application-Id': 'test',
3026+
'X-Parse-REST-API-Key': 'rest',
3027+
'X-Parse-Session-Token': user.getSessionToken(),
3028+
},
3029+
body: JSON.stringify({ 'authData.anonymous".id': 'injected' }),
3030+
}).catch(e => e);
3031+
expect(res.status).toBe(400);
3032+
});
3033+
3034+
it('login does not crash when stored authData has unknown provider', async () => {
3035+
const user = new Parse.User();
3036+
user.setUsername('dotuser2');
3037+
user.setPassword('pass1234');
3038+
await user.signUp();
3039+
await Parse.User.logOut();
3040+
3041+
// Inject unknown provider directly in database to simulate corrupted data
3042+
const config = Config.get('test');
3043+
await config.database.update(
3044+
'_User',
3045+
{ objectId: user.id },
3046+
{ authData: { unknown_provider: { id: 'bad' } } }
3047+
);
3048+
3049+
// Login should not crash with 500
3050+
const login = await request({
3051+
method: 'GET',
3052+
url: `http://localhost:8378/1/login?username=dotuser2&password=pass1234`,
3053+
headers: {
3054+
'X-Parse-Application-Id': 'test',
3055+
'X-Parse-REST-API-Key': 'rest',
3056+
},
3057+
}).catch(e => e);
3058+
expect(login.status).toBe(200);
3059+
expect(login.data.sessionToken).toBeDefined();
3060+
});
3061+
});
3062+
3063+
describe('(GHSA-2c6m-7356-pw67) Challenge null authData dereference', () => {
3064+
it('rejects challenge request with null provider value without 500', async () => {
3065+
const res = await request({
3066+
method: 'POST',
3067+
url: 'http://localhost:8378/1/challenge',
3068+
headers: {
3069+
'Content-Type': 'application/json',
3070+
'X-Parse-Application-Id': 'test',
3071+
'X-Parse-REST-API-Key': 'rest',
3072+
},
3073+
body: JSON.stringify({
3074+
authData: { anonymous: null },
3075+
challengeData: { anonymous: { token: '123456' } },
3076+
}),
3077+
}).catch(e => e);
3078+
expect(res.status).toBeLessThan(500);
3079+
});
3080+
3081+
it('rejects challenge request with non-object provider value without 500', async () => {
3082+
const res = await request({
3083+
method: 'POST',
3084+
url: 'http://localhost:8378/1/challenge',
3085+
headers: {
3086+
'Content-Type': 'application/json',
3087+
'X-Parse-Application-Id': 'test',
3088+
'X-Parse-REST-API-Key': 'rest',
3089+
},
3090+
body: JSON.stringify({
3091+
authData: { anonymous: 'string_value' },
3092+
challengeData: { anonymous: { token: '123456' } },
3093+
}),
3094+
}).catch(e => e);
3095+
expect(res.status).toBeLessThan(500);
3096+
});
3097+
});
3098+
30133099
describe('(GHSA-r3xq-68wh-gwvh) Password reset single-use token bypass via concurrent requests', () => {
30143100
let sendPasswordResetEmail;
30153101

src/Auth.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -523,10 +523,15 @@ const checkIfUserHasProvidedConfiguredProvidersForLogin = (
523523
userAuthData = {},
524524
config
525525
) => {
526-
const savedUserProviders = Object.keys(userAuthData).map(provider => ({
527-
name: provider,
528-
adapter: config.authDataManager.getValidatorForProvider(provider).adapter,
529-
}));
526+
const savedUserProviders = Object.keys(userAuthData)
527+
.map(provider => {
528+
const validator = config.authDataManager.getValidatorForProvider(provider);
529+
if (!validator) {
530+
return null;
531+
}
532+
return { name: provider, adapter: validator.adapter };
533+
})
534+
.filter(Boolean);
530535

531536
const hasProvidedASoloProvider = savedUserProviders.some(
532537
provider =>

src/Controllers/DatabaseController.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,7 @@ class DatabaseController {
603603
})
604604
.then(schema => {
605605
Object.keys(update).forEach(fieldName => {
606-
if (fieldName.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) {
606+
if (fieldName.match(/^authData\./)) {
607607
throw new Parse.Error(
608608
Parse.Error.INVALID_KEY_NAME,
609609
`Invalid field name for update: ${fieldName}`

src/Routers/UsersRouter.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,16 @@ export class UsersRouter extends ClassesRouter {
614614
);
615615
}
616616

617-
if (Object.keys(authData).filter(key => authData[key].id).length > 1) {
617+
for (const key of Object.keys(authData)) {
618+
if (authData[key] !== null && (typeof authData[key] !== 'object' || Array.isArray(authData[key]))) {
619+
throw new Parse.Error(
620+
Parse.Error.OTHER_CAUSE,
621+
`authData.${key} should be an object.`
622+
);
623+
}
624+
}
625+
626+
if (Object.keys(authData).filter(key => authData[key] && authData[key].id).length > 1) {
618627
throw new Parse.Error(
619628
Parse.Error.OTHER_CAUSE,
620629
'You cannot provide more than one authData provider with an id.'
@@ -628,7 +637,7 @@ export class UsersRouter extends ClassesRouter {
628637
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.');
629638
}
630639
// Find the provider used to find the user
631-
const provider = Object.keys(authData).find(key => authData[key].id);
640+
const provider = Object.keys(authData).find(key => authData[key] && authData[key].id);
632641

633642
parseUser = Parse.User.fromJSON({ className: '_User', ...results[0] });
634643
request = getRequestObject(undefined, req.auth, parseUser, parseUser, req.config);

0 commit comments

Comments
 (0)