Skip to content

Commit f06e857

Browse files
committed
1 parent 122f4ac commit f06e857

2 files changed

Lines changed: 95 additions & 14 deletions

File tree

spec/vulnerabilities.spec.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2700,6 +2700,82 @@ describe('(GHSA-9xp9-j92r-p88v) Stack overflow process crash via deeply nested q
27002700
);
27012701
});
27022702

2703+
describe('(GHSA-wjqw-r9x4-j59v) Empty authData session issuance bypass', () => {
2704+
const signupHeaders = {
2705+
'Content-Type': 'application/json',
2706+
'X-Parse-Application-Id': 'test',
2707+
'X-Parse-REST-API-Key': 'rest',
2708+
};
2709+
2710+
it('rejects signup with empty authData and no credentials', async () => {
2711+
await reconfigureServer({ enableAnonymousUsers: false });
2712+
const res = await request({
2713+
method: 'POST',
2714+
url: 'http://localhost:8378/1/users',
2715+
headers: signupHeaders,
2716+
body: JSON.stringify({ authData: {} }),
2717+
}).catch(e => e);
2718+
expect(res.status).toBe(400);
2719+
expect(res.data.code).toBe(Parse.Error.USERNAME_MISSING);
2720+
});
2721+
2722+
it('rejects signup with empty authData and no credentials when anonymous users enabled', async () => {
2723+
await reconfigureServer({ enableAnonymousUsers: true });
2724+
const res = await request({
2725+
method: 'POST',
2726+
url: 'http://localhost:8378/1/users',
2727+
headers: signupHeaders,
2728+
body: JSON.stringify({ authData: {} }),
2729+
}).catch(e => e);
2730+
expect(res.status).toBe(400);
2731+
expect(res.data.code).toBe(Parse.Error.USERNAME_MISSING);
2732+
});
2733+
2734+
it('rejects signup with authData containing only empty provider data and no credentials', async () => {
2735+
const res = await request({
2736+
method: 'POST',
2737+
url: 'http://localhost:8378/1/users',
2738+
headers: signupHeaders,
2739+
body: JSON.stringify({ authData: { bogus: {} } }),
2740+
}).catch(e => e);
2741+
expect(res.status).toBe(400);
2742+
expect(res.data.code).toBe(Parse.Error.USERNAME_MISSING);
2743+
});
2744+
2745+
it('rejects signup with authData containing null provider data and no credentials', async () => {
2746+
const res = await request({
2747+
method: 'POST',
2748+
url: 'http://localhost:8378/1/users',
2749+
headers: signupHeaders,
2750+
body: JSON.stringify({ authData: { bogus: null } }),
2751+
}).catch(e => e);
2752+
expect(res.status).toBe(400);
2753+
expect(res.data.code).toBe(Parse.Error.USERNAME_MISSING);
2754+
});
2755+
2756+
it('rejects signup with non-object authData provider value even when credentials are provided', async () => {
2757+
const res = await request({
2758+
method: 'POST',
2759+
url: 'http://localhost:8378/1/users',
2760+
headers: signupHeaders,
2761+
body: JSON.stringify({ username: 'bogusauth', password: 'pass1234', authData: { bogus: 'x' } }),
2762+
}).catch(e => e);
2763+
expect(res.status).toBe(400);
2764+
expect(res.data.code).toBe(Parse.Error.UNSUPPORTED_SERVICE);
2765+
});
2766+
2767+
it('allows signup with empty authData when username and password are provided', async () => {
2768+
const res = await request({
2769+
method: 'POST',
2770+
url: 'http://localhost:8378/1/users',
2771+
headers: signupHeaders,
2772+
body: JSON.stringify({ username: 'emptyauth', password: 'pass1234', authData: {} }),
2773+
});
2774+
expect(res.data.objectId).toBeDefined();
2775+
expect(res.data.sessionToken).toBeDefined();
2776+
});
2777+
});
2778+
27032779
describe('(GHSA-r3xq-68wh-gwvh) Password reset single-use token bypass via concurrent requests', () => {
27042780
let sendPasswordResetEmail;
27052781

src/RestWrite.js

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

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

466-
if (
467-
(authData && !Object.keys(authData).length) ||
468-
!Object.prototype.hasOwnProperty.call(this.data, 'authData')
469-
) {
472+
if (!Object.prototype.hasOwnProperty.call(this.data, 'authData')) {
470473
// Nothing to validate here
471474
return;
472-
} else if (Object.prototype.hasOwnProperty.call(this.data, 'authData') && !this.data.authData) {
475+
} else if (!this.data.authData) {
473476
// Handle saving authData to null
474477
throw new Parse.Error(
475478
Parse.Error.UNSUPPORTED_SERVICE,
@@ -478,14 +481,16 @@ RestWrite.prototype.validateAuthData = function () {
478481
}
479482

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

0 commit comments

Comments
 (0)