VYPR
High severityNVD Advisory· Published Mar 24, 2026· Updated Mar 25, 2026

Parse Server: Auth data exposed via /users/me endpoint

CVE-2026-33627

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.61 and 9.6.0-alpha.55, an authenticated user calling GET /users/me receives unsanitized auth data, including sensitive credentials such as MFA TOTP secrets and recovery codes. The endpoint internally uses master-level authentication for the session query, and the master context leaks through to the user data, bypassing auth adapter sanitization. An attacker who obtains a user's session token can extract MFA secrets to generate valid TOTP codes indefinitely. This issue has been patched in versions 8.6.61 and 9.6.0-alpha.55.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
parse-servernpm
>= 9.0.0, < 9.6.0-alpha.559.6.0-alpha.55
parse-servernpm
< 8.6.618.6.61

Affected products

1

Patches

2
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

7

News mentions

0

No linked articles in our index yet.