fix: Auth data exposed via verify password endpoint ([GHSA-wp76-gg32-8258](https://github.com/parse-community/parse-server/security/advisories/GHSA-wp76-gg32-8258)) (#10324)

This commit is contained in:
Manuel
2026-03-26 20:35:35 +00:00
committed by GitHub
parent 4ff8b79922
commit a1d4e7b12a
2 changed files with 93 additions and 2 deletions
+91
View File
@@ -4142,3 +4142,94 @@ describe('(GHSA-37mj-c2wf-cx96) /users/me leaks raw authData via master context'
expect(meResponse.data.authData?.mfa).toEqual({ status: 'enabled' });
});
});
describe('(GHSA-wp76-gg32-8258) /verifyPassword leaks raw authData via missing afterFind', () => {
const headers = {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'Content-Type': 'application/json',
};
it('does not leak raw MFA authData via /verifyPassword', async () => {
await reconfigureServer({
auth: {
mfa: {
enabled: true,
options: ['TOTP'],
algorithm: 'SHA1',
digits: 6,
period: 30,
},
},
verifyUserEmails: false,
});
const user = await Parse.User.signUp('username', 'password');
const sessionToken = user.getSessionToken();
const OTPAuth = require('otpauth');
const secret = new OTPAuth.Secret();
const totp = new OTPAuth.TOTP({
algorithm: 'SHA1',
digits: 6,
period: 30,
secret,
});
const token = totp.generate();
// Enable MFA
await user.save(
{ authData: { mfa: { secret: secret.base32, token } } },
{ sessionToken }
);
// Verify MFA data is stored (master key)
await user.fetch({ useMasterKey: true });
expect(user.get('authData').mfa.secret).toBe(secret.base32);
expect(user.get('authData').mfa.recovery).toBeDefined();
// POST /verifyPassword should NOT include raw MFA data
const response = await request({
headers,
method: 'POST',
url: 'http://localhost:8378/1/verifyPassword',
body: JSON.stringify({ username: 'username', password: 'password' }),
});
expect(response.data.authData?.mfa?.secret).toBeUndefined();
expect(response.data.authData?.mfa?.recovery).toBeUndefined();
expect(response.data.authData?.mfa).toEqual({ status: 'enabled' });
});
it('does not leak raw MFA authData via GET /verifyPassword', async () => {
await reconfigureServer({
auth: {
mfa: {
enabled: true,
options: ['TOTP'],
algorithm: 'SHA1',
digits: 6,
period: 30,
},
},
verifyUserEmails: false,
});
const user = await Parse.User.signUp('username', 'password');
const sessionToken = user.getSessionToken();
const OTPAuth = require('otpauth');
const secret = new OTPAuth.Secret();
const totp = new OTPAuth.TOTP({
algorithm: 'SHA1',
digits: 6,
period: 30,
secret,
});
await user.save(
{ authData: { mfa: { secret: secret.base32, token: totp.generate() } } },
{ sessionToken }
);
// GET /verifyPassword should NOT include raw MFA data
const response = await request({
headers,
method: 'GET',
url: `http://localhost:8378/1/verifyPassword?username=username&password=password`,
});
expect(response.data.authData?.mfa?.secret).toBeUndefined();
expect(response.data.authData?.mfa?.recovery).toBeUndefined();
expect(response.data.authData?.mfa).toEqual({ status: 'enabled' });
});
});
+2 -2
View File
@@ -422,10 +422,10 @@ export class UsersRouter extends ClassesRouter {
handleVerifyPassword(req) {
return this._authenticateUserFromRequest(req)
.then(user => {
.then(async user => {
// Remove hidden properties.
UsersRouter.removeHiddenProperties(user);
await req.config.authDataManager.runAfterFind(req, user.authData);
return { response: user };
})
.catch(error => {