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)) (#10323)

This commit is contained in:
Manuel
2026-03-26 20:35:44 +00:00
committed by GitHub
parent f537e677f0
commit 770be86474
2 changed files with 93 additions and 2 deletions
+91
View File
@@ -4600,4 +4600,95 @@ describe('(GHSA-p2w6-rmh7-w8q3) SQL Injection via aggregate and distinct field n
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 => {