Skip to content

Commit 5dcbf41

Browse files
authored
fix: Empty authData bypasses credential requirement on signup ([GHSA-wjqw-r9x4-j59v](GHSA-wjqw-r9x4-j59v)) (#10219)
1 parent 6476c57 commit 5dcbf41

File tree

2 files changed

+95
-14
lines changed

2 files changed

+95
-14
lines changed

spec/vulnerabilities.spec.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2934,6 +2934,82 @@ describe('(GHSA-fjxm-vhvc-gcmj) LiveQuery Operator Type Confusion', () => {
29342934
});
29352935
});
29362936

2937+
describe('(GHSA-wjqw-r9x4-j59v) Empty authData session issuance bypass', () => {
2938+
const signupHeaders = {
2939+
'Content-Type': 'application/json',
2940+
'X-Parse-Application-Id': 'test',
2941+
'X-Parse-REST-API-Key': 'rest',
2942+
};
2943+
2944+
it('rejects signup with empty authData and no credentials', async () => {
2945+
await reconfigureServer({ enableAnonymousUsers: false });
2946+
const res = await request({
2947+
method: 'POST',
2948+
url: 'http://localhost:8378/1/users',
2949+
headers: signupHeaders,
2950+
body: JSON.stringify({ authData: {} }),
2951+
}).catch(e => e);
2952+
expect(res.status).toBe(400);
2953+
expect(res.data.code).toBe(Parse.Error.USERNAME_MISSING);
2954+
});
2955+
2956+
it('rejects signup with empty authData and no credentials when anonymous users enabled', async () => {
2957+
await reconfigureServer({ enableAnonymousUsers: true });
2958+
const res = await request({
2959+
method: 'POST',
2960+
url: 'http://localhost:8378/1/users',
2961+
headers: signupHeaders,
2962+
body: JSON.stringify({ authData: {} }),
2963+
}).catch(e => e);
2964+
expect(res.status).toBe(400);
2965+
expect(res.data.code).toBe(Parse.Error.USERNAME_MISSING);
2966+
});
2967+
2968+
it('rejects signup with authData containing only empty provider data and no credentials', async () => {
2969+
const res = await request({
2970+
method: 'POST',
2971+
url: 'http://localhost:8378/1/users',
2972+
headers: signupHeaders,
2973+
body: JSON.stringify({ authData: { bogus: {} } }),
2974+
}).catch(e => e);
2975+
expect(res.status).toBe(400);
2976+
expect(res.data.code).toBe(Parse.Error.USERNAME_MISSING);
2977+
});
2978+
2979+
it('rejects signup with authData containing null provider data and no credentials', async () => {
2980+
const res = await request({
2981+
method: 'POST',
2982+
url: 'http://localhost:8378/1/users',
2983+
headers: signupHeaders,
2984+
body: JSON.stringify({ authData: { bogus: null } }),
2985+
}).catch(e => e);
2986+
expect(res.status).toBe(400);
2987+
expect(res.data.code).toBe(Parse.Error.USERNAME_MISSING);
2988+
});
2989+
2990+
it('rejects signup with non-object authData provider value even when credentials are provided', async () => {
2991+
const res = await request({
2992+
method: 'POST',
2993+
url: 'http://localhost:8378/1/users',
2994+
headers: signupHeaders,
2995+
body: JSON.stringify({ username: 'bogusauth', password: 'pass1234', authData: { bogus: 'x' } }),
2996+
}).catch(e => e);
2997+
expect(res.status).toBe(400);
2998+
expect(res.data.code).toBe(Parse.Error.UNSUPPORTED_SERVICE);
2999+
});
3000+
3001+
it('allows signup with empty authData when username and password are provided', async () => {
3002+
const res = await request({
3003+
method: 'POST',
3004+
url: 'http://localhost:8378/1/users',
3005+
headers: signupHeaders,
3006+
body: JSON.stringify({ username: 'emptyauth', password: 'pass1234', authData: {} }),
3007+
});
3008+
expect(res.data.objectId).toBeDefined();
3009+
expect(res.data.sessionToken).toBeDefined();
3010+
});
3011+
});
3012+
29373013
describe('(GHSA-r3xq-68wh-gwvh) Password reset single-use token bypass via concurrent requests', () => {
29383014
let sendPasswordResetEmail;
29393015

src/RestWrite.js

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -452,8 +452,14 @@ RestWrite.prototype.validateAuthData = function () {
452452
const authData = this.data.authData;
453453
const hasUsernameAndPassword =
454454
typeof this.data.username === 'string' && typeof this.data.password === 'string';
455+
const hasAuthData =
456+
authData &&
457+
Object.keys(authData).some(provider => {
458+
const providerData = authData[provider];
459+
return providerData && typeof providerData === 'object' && Object.keys(providerData).length;
460+
});
455461

456-
if (!this.query && !authData) {
462+
if (!this.query && !hasAuthData) {
457463
if (typeof this.data.username !== 'string' || _.isEmpty(this.data.username)) {
458464
throw new Parse.Error(Parse.Error.USERNAME_MISSING, 'bad or missing username');
459465
}
@@ -462,13 +468,10 @@ RestWrite.prototype.validateAuthData = function () {
462468
}
463469
}
464470

465-
if (
466-
(authData && !Object.keys(authData).length) ||
467-
!Object.prototype.hasOwnProperty.call(this.data, 'authData')
468-
) {
471+
if (!Object.prototype.hasOwnProperty.call(this.data, 'authData')) {
469472
// Nothing to validate here
470473
return;
471-
} else if (Object.prototype.hasOwnProperty.call(this.data, 'authData') && !this.data.authData) {
474+
} else if (!this.data.authData) {
472475
// Handle saving authData to null
473476
throw new Parse.Error(
474477
Parse.Error.UNSUPPORTED_SERVICE,
@@ -477,14 +480,16 @@ RestWrite.prototype.validateAuthData = function () {
477480
}
478481

479482
var providers = Object.keys(authData);
480-
if (providers.length > 0) {
481-
const canHandleAuthData = providers.some(provider => {
482-
const providerAuthData = authData[provider] || {};
483-
return !!Object.keys(providerAuthData).length;
484-
});
485-
if (canHandleAuthData || hasUsernameAndPassword || this.auth.isMaster || this.getUserId()) {
486-
return this.handleAuthData(authData);
487-
}
483+
if (!providers.length) {
484+
// Empty authData object, nothing to validate
485+
return;
486+
}
487+
const canHandleAuthData = providers.some(provider => {
488+
const providerAuthData = authData[provider] || {};
489+
return !!Object.keys(providerAuthData).length;
490+
});
491+
if (canHandleAuthData || hasUsernameAndPassword || this.auth.isMaster || this.getUserId()) {
492+
return this.handleAuthData(authData);
488493
}
489494
throw new Parse.Error(
490495
Parse.Error.UNSUPPORTED_SERVICE,

0 commit comments

Comments
 (0)