VYPR
Medium severity6.5NVD Advisory· Published Mar 31, 2026· Updated Apr 3, 2026

CVE-2026-34215

CVE-2026-34215

Description

Parse Server is an open source backend that can be deployed to any infrastructure that can run Node.js. Prior to versions 8.6.63 and 9.7.0-alpha.7, the verify password endpoint returns unsanitized authentication data, including MFA TOTP secrets, recovery codes, and OAuth access tokens. An attacker who knows a user's password can extract the MFA secret to generate valid MFA codes, defeating multi-factor authentication protection. This issue has been patched in versions 8.6.63 and 9.7.0-alpha.7.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
parse-servernpm
>= 9.0.0, < 9.7.0-alpha.79.7.0-alpha.7
parse-servernpm
< 8.6.638.6.63

Affected products

7
  • cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha1:*:*:*:node.js:*:*+ 6 more
    • cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha1:*:*:*:node.js:*:*
    • cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha2:*:*:*:node.js:*:*
    • cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha3:*:*:*:node.js:*:*
    • cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha4:*:*:*:node.js:*:*
    • cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha5:*:*:*:node.js:*:*
    • cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha6:*:*:*:node.js:*:*
    • cpe:2.3:a:parseplatform:parse-server:*:*:*:*:*:node.js:*:*range: <8.6.63

Patches

4
770be8647424

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)

2 files changed · +93 2
  • spec/vulnerabilities.spec.js+91 0 modified
    @@ -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' });
    +    });
    +  });
     });
    
  • src/Routers/UsersRouter.js+2 2 modified
    @@ -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 => {
    
a1d4e7b12a12

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)

2 files changed · +93 2
  • spec/vulnerabilities.spec.js+91 0 modified
    @@ -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' });
    +  });
    +});
    
  • src/Routers/UsersRouter.js+2 2 modified
    @@ -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 => {
    
875cf10ac979

fix: Auth data exposed via /users/me endpoint ([GHSA-37mj-c2wf-cx96](https://github.com/parse-community/parse-server/security/advisories/GHSA-37mj-c2wf-cx96)) (#10278)

2 files changed · +143 24
  • spec/vulnerabilities.spec.js+103 0 modified
    @@ -4497,4 +4497,107 @@ describe('(GHSA-p2w6-rmh7-w8q3) SQL Injection via aggregate and distinct field n
           expect(response.data?.results).toEqual(['Alice']);
         });
       });
    +
    +  describe('(GHSA-37mj-c2wf-cx96) /users/me leaks raw authData via master context', () => {
    +    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 /users/me', async () => {
    +      await reconfigureServer({
    +        auth: {
    +          mfa: {
    +            enabled: true,
    +            options: ['TOTP'],
    +            algorithm: 'SHA1',
    +            digits: 6,
    +            period: 30,
    +          },
    +        },
    +      });
    +      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();
    +      // GET /users/me should NOT include raw MFA data
    +      const response = await request({
    +        headers: {
    +          ...headers,
    +          'X-Parse-Session-Token': sessionToken,
    +        },
    +        method: 'GET',
    +        url: 'http://localhost:8378/1/users/me',
    +      });
    +      expect(response.data.authData?.mfa?.secret).toBeUndefined();
    +      expect(response.data.authData?.mfa?.recovery).toBeUndefined();
    +      expect(response.data.authData?.mfa).toEqual({ status: 'enabled' });
    +    });
    +
    +    it('returns same authData from /users/me and /users/:id', async () => {
    +      await reconfigureServer({
    +        auth: {
    +          mfa: {
    +            enabled: true,
    +            options: ['TOTP'],
    +            algorithm: 'SHA1',
    +            digits: 6,
    +            period: 30,
    +          },
    +        },
    +      });
    +      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 }
    +      );
    +      // Fetch via /users/me
    +      const meResponse = await request({
    +        headers: {
    +          ...headers,
    +          'X-Parse-Session-Token': sessionToken,
    +        },
    +        method: 'GET',
    +        url: 'http://localhost:8378/1/users/me',
    +      });
    +      // Fetch via /users/:id
    +      const idResponse = await request({
    +        headers: {
    +          ...headers,
    +          'X-Parse-Session-Token': sessionToken,
    +        },
    +        method: 'GET',
    +        url: `http://localhost:8378/1/users/${user.id}`,
    +      });
    +      // Both should return the same sanitized authData
    +      expect(meResponse.data.authData).toEqual(idResponse.data.authData);
    +      expect(meResponse.data.authData?.mfa).toEqual({ status: 'enabled' });
    +    });
    +  });
     });
    
  • src/Routers/UsersRouter.js+40 24 modified
    @@ -176,34 +176,50 @@ export class UsersRouter extends ClassesRouter {
         });
       }
     
    -  handleMe(req) {
    +  async handleMe(req) {
         if (!req.info || !req.info.sessionToken) {
           throw createSanitizedError(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token', req.config);
         }
         const sessionToken = req.info.sessionToken;
    -    return rest
    -      .find(
    -        req.config,
    -        Auth.master(req.config),
    -        '_Session',
    -        { sessionToken },
    -        { include: 'user' },
    -        req.info.clientSDK,
    -        req.info.context
    -      )
    -      .then(response => {
    -        if (!response.results || response.results.length == 0 || !response.results[0].user) {
    -          throw createSanitizedError(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token', req.config);
    -        } else {
    -          const user = response.results[0].user;
    -          // Send token back on the login, because SDKs expect that.
    -          user.sessionToken = sessionToken;
    -
    -          // Remove hidden properties.
    -          UsersRouter.removeHiddenProperties(user);
    -          return { response: user };
    -        }
    -      });
    +    // Query the session with master key to validate the session token,
    +    // but do NOT include 'user' to avoid leaking user data via master context
    +    const sessionResponse = await rest.find(
    +      req.config,
    +      Auth.master(req.config),
    +      '_Session',
    +      { sessionToken },
    +      {},
    +      req.info.clientSDK,
    +      req.info.context
    +    );
    +    if (
    +      !sessionResponse.results ||
    +      sessionResponse.results.length == 0 ||
    +      !sessionResponse.results[0].user
    +    ) {
    +      throw createSanitizedError(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token', req.config);
    +    }
    +    const userId = sessionResponse.results[0].user.objectId;
    +    // Re-fetch the user with the caller's auth context so that
    +    // protectedFields, CLP, and auth adapter afterFind apply correctly
    +    const userResponse = await rest.get(
    +      req.config,
    +      req.auth,
    +      '_User',
    +      userId,
    +      {},
    +      req.info.clientSDK,
    +      req.info.context
    +    );
    +    if (!userResponse.results || userResponse.results.length == 0) {
    +      throw createSanitizedError(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token', req.config);
    +    }
    +    const user = userResponse.results[0];
    +    // Send token back on the login, because SDKs expect that.
    +    user.sessionToken = sessionToken;
    +    // Remove hidden properties.
    +    UsersRouter.removeHiddenProperties(user);
    +    return { response: user };
       }
     
       async handleLogIn(req) {
    
5b8998e6866b

fix: Auth data exposed via /users/me endpoint ([GHSA-37mj-c2wf-cx96](https://github.com/parse-community/parse-server/security/advisories/GHSA-37mj-c2wf-cx96)) (#10279)

2 files changed · +143 24
  • spec/vulnerabilities.spec.js+103 0 modified
    @@ -4039,3 +4039,106 @@ describe('(GHSA-2299-ghjr-6vjp) MFA recovery code reuse via concurrent requests'
         expect(remainingRecovery).not.toContain(recoveryCode);
       });
     });
    +
    +describe('(GHSA-37mj-c2wf-cx96) /users/me leaks raw authData via master context', () => {
    +  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 /users/me', async () => {
    +    await reconfigureServer({
    +      auth: {
    +        mfa: {
    +          enabled: true,
    +          options: ['TOTP'],
    +          algorithm: 'SHA1',
    +          digits: 6,
    +          period: 30,
    +        },
    +      },
    +    });
    +    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();
    +    // GET /users/me should NOT include raw MFA data
    +    const response = await request({
    +      headers: {
    +        ...headers,
    +        'X-Parse-Session-Token': sessionToken,
    +      },
    +      method: 'GET',
    +      url: 'http://localhost:8378/1/users/me',
    +    });
    +    expect(response.data.authData?.mfa?.secret).toBeUndefined();
    +    expect(response.data.authData?.mfa?.recovery).toBeUndefined();
    +    expect(response.data.authData?.mfa).toEqual({ status: 'enabled' });
    +  });
    +
    +  it('returns same authData from /users/me and /users/:id', async () => {
    +    await reconfigureServer({
    +      auth: {
    +        mfa: {
    +          enabled: true,
    +          options: ['TOTP'],
    +          algorithm: 'SHA1',
    +          digits: 6,
    +          period: 30,
    +        },
    +      },
    +    });
    +    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 }
    +    );
    +    // Fetch via /users/me
    +    const meResponse = await request({
    +      headers: {
    +        ...headers,
    +        'X-Parse-Session-Token': sessionToken,
    +      },
    +      method: 'GET',
    +      url: 'http://localhost:8378/1/users/me',
    +    });
    +    // Fetch via /users/:id
    +    const idResponse = await request({
    +      headers: {
    +        ...headers,
    +        'X-Parse-Session-Token': sessionToken,
    +      },
    +      method: 'GET',
    +      url: `http://localhost:8378/1/users/${user.id}`,
    +    });
    +    // Both should return the same sanitized authData
    +    expect(meResponse.data.authData).toEqual(idResponse.data.authData);
    +    expect(meResponse.data.authData?.mfa).toEqual({ status: 'enabled' });
    +  });
    +});
    
  • src/Routers/UsersRouter.js+40 24 modified
    @@ -170,34 +170,50 @@ export class UsersRouter extends ClassesRouter {
         });
       }
     
    -  handleMe(req) {
    +  async handleMe(req) {
         if (!req.info || !req.info.sessionToken) {
           throw createSanitizedError(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token', req.config);
         }
         const sessionToken = req.info.sessionToken;
    -    return rest
    -      .find(
    -        req.config,
    -        Auth.master(req.config),
    -        '_Session',
    -        { sessionToken },
    -        { include: 'user' },
    -        req.info.clientSDK,
    -        req.info.context
    -      )
    -      .then(response => {
    -        if (!response.results || response.results.length == 0 || !response.results[0].user) {
    -          throw createSanitizedError(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token', req.config);
    -        } else {
    -          const user = response.results[0].user;
    -          // Send token back on the login, because SDKs expect that.
    -          user.sessionToken = sessionToken;
    -
    -          // Remove hidden properties.
    -          UsersRouter.removeHiddenProperties(user);
    -          return { response: user };
    -        }
    -      });
    +    // Query the session with master key to validate the session token,
    +    // but do NOT include 'user' to avoid leaking user data via master context
    +    const sessionResponse = await rest.find(
    +      req.config,
    +      Auth.master(req.config),
    +      '_Session',
    +      { sessionToken },
    +      {},
    +      req.info.clientSDK,
    +      req.info.context
    +    );
    +    if (
    +      !sessionResponse.results ||
    +      sessionResponse.results.length == 0 ||
    +      !sessionResponse.results[0].user
    +    ) {
    +      throw createSanitizedError(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token', req.config);
    +    }
    +    const userId = sessionResponse.results[0].user.objectId;
    +    // Re-fetch the user with the caller's auth context so that
    +    // protectedFields, CLP, and auth adapter afterFind apply correctly
    +    const userResponse = await rest.get(
    +      req.config,
    +      req.auth,
    +      '_User',
    +      userId,
    +      {},
    +      req.info.clientSDK,
    +      req.info.context
    +    );
    +    if (!userResponse.results || userResponse.results.length == 0) {
    +      throw createSanitizedError(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token', req.config);
    +    }
    +    const user = userResponse.results[0];
    +    // Send token back on the login, because SDKs expect that.
    +    user.sessionToken = sessionToken;
    +    // Remove hidden properties.
    +    UsersRouter.removeHiddenProperties(user);
    +    return { response: user };
       }
     
       async handleLogIn(req) {
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

11

News mentions

0

No linked articles in our index yet.