Skip to content

Commit e5e1f5b

Browse files
authored
fix: Validate authData provider values in challenge endpoint (#10224)
1 parent 5a58583 commit e5e1f5b

File tree

3 files changed

+100
-52
lines changed

3 files changed

+100
-52
lines changed

spec/AuthenticationAdaptersV2.spec.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const request = require('../lib/request');
22
const Auth = require('../lib/Auth');
3+
const Config = require('../lib/Config');
34
const requestWithExpectedError = async params => {
45
try {
56
return await request(params);
@@ -1613,4 +1614,92 @@ describe('Auth Adapter features', () => {
16131614
expect(authData.simpleAdapter && authData.simpleAdapter.id).toBe('simple1');
16141615
expect(authData.codeBasedAdapter && authData.codeBasedAdapter.id).toBe('user1');
16151616
});
1617+
1618+
describe('authData dot-notation injection and login crash', () => {
1619+
it('rejects dotted update key that targets authData sub-field', async () => {
1620+
const user = new Parse.User();
1621+
user.setUsername('dotuser');
1622+
user.setPassword('pass1234');
1623+
await user.signUp();
1624+
1625+
const res = await request({
1626+
method: 'PUT',
1627+
url: `http://localhost:8378/1/users/${user.id}`,
1628+
headers: {
1629+
'Content-Type': 'application/json',
1630+
'X-Parse-Application-Id': 'test',
1631+
'X-Parse-REST-API-Key': 'rest',
1632+
'X-Parse-Session-Token': user.getSessionToken(),
1633+
},
1634+
body: JSON.stringify({ 'authData.anonymous".id': 'injected' }),
1635+
}).catch(e => e);
1636+
expect(res.status).toBe(400);
1637+
});
1638+
1639+
it('login does not crash when stored authData has unknown provider', async () => {
1640+
const user = new Parse.User();
1641+
user.setUsername('dotuser2');
1642+
user.setPassword('pass1234');
1643+
await user.signUp();
1644+
await Parse.User.logOut();
1645+
1646+
// Inject unknown provider directly in database to simulate corrupted data
1647+
const config = Config.get('test');
1648+
await config.database.update(
1649+
'_User',
1650+
{ objectId: user.id },
1651+
{ authData: { unknown_provider: { id: 'bad' } } }
1652+
);
1653+
1654+
// Login should not crash with 500
1655+
const login = await request({
1656+
method: 'GET',
1657+
url: `http://localhost:8378/1/login?username=dotuser2&password=pass1234`,
1658+
headers: {
1659+
'X-Parse-Application-Id': 'test',
1660+
'X-Parse-REST-API-Key': 'rest',
1661+
},
1662+
}).catch(e => e);
1663+
expect(login.status).toBe(200);
1664+
expect(login.data.sessionToken).toBeDefined();
1665+
});
1666+
});
1667+
1668+
describe('challenge endpoint authData provider value validation', () => {
1669+
it('rejects challenge request with null provider value without 500', async () => {
1670+
const res = await request({
1671+
method: 'POST',
1672+
url: 'http://localhost:8378/1/challenge',
1673+
headers: {
1674+
'Content-Type': 'application/json',
1675+
'X-Parse-Application-Id': 'test',
1676+
'X-Parse-REST-API-Key': 'rest',
1677+
},
1678+
body: JSON.stringify({
1679+
authData: { anonymous: null },
1680+
challengeData: { anonymous: { token: '123456' } },
1681+
}),
1682+
}).catch(e => e);
1683+
expect(res.status).toBeGreaterThanOrEqual(400);
1684+
expect(res.status).toBeLessThan(500);
1685+
});
1686+
1687+
it('rejects challenge request with non-object provider value without 500', async () => {
1688+
const res = await request({
1689+
method: 'POST',
1690+
url: 'http://localhost:8378/1/challenge',
1691+
headers: {
1692+
'Content-Type': 'application/json',
1693+
'X-Parse-Application-Id': 'test',
1694+
'X-Parse-REST-API-Key': 'rest',
1695+
},
1696+
body: JSON.stringify({
1697+
authData: { anonymous: 'string_value' },
1698+
challengeData: { anonymous: { token: '123456' } },
1699+
}),
1700+
}).catch(e => e);
1701+
expect(res.status).toBeGreaterThanOrEqual(400);
1702+
expect(res.status).toBeLessThan(500);
1703+
});
1704+
});
16161705
});

spec/vulnerabilities.spec.js

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

3013-
describe('authData dot-notation injection and login crash', () => {
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-
30633013
describe('(GHSA-r3xq-68wh-gwvh) Password reset single-use token bypass via concurrent requests', () => {
30643014
let sendPasswordResetEmail;
30653015

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)