VYPR
High severityNVD Advisory· Published Mar 4, 2020· Updated Aug 4, 2024

Information disclosure in parse-server

CVE-2020-5251

Description

In parser-server before version 4.1.0, you can fetch all the users objects, by using regex in the NoSQL query. Using the NoSQL, you can use a regex on sessionToken and find valid accounts this way.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
parse-servernpm
< 4.1.04.1.0

Affected products

1

Patches

1
3a3a5eee5ffa

Merge pull request from GHSA-h4mf-75hf-67w4

https://github.com/parse-community/parse-serverArthur CinaderMar 2, 2020via ghsa
3 files changed · +212 3
  • spec/RegexVulnerabilities.spec.js+198 0 added
    @@ -0,0 +1,198 @@
    +const request = require('../lib/request');
    +
    +const serverURL = 'http://localhost:8378/1';
    +const headers = {
    +  'Content-Type': 'application/json',
    +};
    +const keys = {
    +  _ApplicationId: 'test',
    +  _JavaScriptKey: 'test',
    +};
    +const emailAdapter = {
    +  sendVerificationEmail: () => Promise.resolve(),
    +  sendPasswordResetEmail: () => Promise.resolve(),
    +  sendMail: () => {},
    +};
    +const appName = 'test';
    +const publicServerURL = 'http://localhost:8378/1';
    +
    +describe('Regex Vulnerabilities', function() {
    +  beforeEach(async function() {
    +    await reconfigureServer({
    +      verifyUserEmails: true,
    +      emailAdapter,
    +      appName,
    +      publicServerURL,
    +    });
    +
    +    const signUpResponse = await request({
    +      url: `${serverURL}/users`,
    +      method: 'POST',
    +      headers,
    +      body: JSON.stringify({
    +        ...keys,
    +        _method: 'POST',
    +        username: 'someemail@somedomain.com',
    +        password: 'somepassword',
    +        email: 'someemail@somedomain.com',
    +      }),
    +    });
    +    this.objectId = signUpResponse.data.objectId;
    +    this.sessionToken = signUpResponse.data.sessionToken;
    +    this.partialSessionToken = this.sessionToken.slice(0, 3);
    +  });
    +
    +  describe('on session token', function() {
    +    it('should not work with regex', async function() {
    +      try {
    +        await request({
    +          url: `${serverURL}/users/me`,
    +          method: 'POST',
    +          headers,
    +          body: JSON.stringify({
    +            ...keys,
    +            _SessionToken: {
    +              $regex: this.partialSessionToken,
    +            },
    +            _method: 'GET',
    +          }),
    +        });
    +        fail('should not work');
    +      } catch (e) {
    +        expect(e.data.code).toEqual(209);
    +        expect(e.data.error).toEqual('Invalid session token');
    +      }
    +    });
    +
    +    it('should work with plain token', async function() {
    +      const meResponse = await request({
    +        url: `${serverURL}/users/me`,
    +        method: 'POST',
    +        headers,
    +        body: JSON.stringify({
    +          ...keys,
    +          _SessionToken: this.sessionToken,
    +          _method: 'GET',
    +        }),
    +      });
    +      expect(meResponse.data.objectId).toEqual(this.objectId);
    +      expect(meResponse.data.sessionToken).toEqual(this.sessionToken);
    +    });
    +  });
    +
    +  describe('on verify e-mail', function() {
    +    beforeEach(async function() {
    +      const userQuery = new Parse.Query(Parse.User);
    +      this.user = await userQuery.get(this.objectId, { useMasterKey: true });
    +    });
    +
    +    it('should not work with regex', async function() {
    +      expect(this.user.get('emailVerified')).toEqual(false);
    +      await request({
    +        url: `${serverURL}/apps/test/verify_email?username=someemail@somedomain.com&token[$regex]=`,
    +        method: 'GET',
    +      });
    +      await this.user.fetch({ useMasterKey: true });
    +      expect(this.user.get('emailVerified')).toEqual(false);
    +    });
    +
    +    it('should work with plain token', async function() {
    +      expect(this.user.get('emailVerified')).toEqual(false);
    +      // It should work
    +      await request({
    +        url: `${serverURL}/apps/test/verify_email?username=someemail@somedomain.com&token=${this.user.get(
    +          '_email_verify_token'
    +        )}`,
    +        method: 'GET',
    +      });
    +      await this.user.fetch({ useMasterKey: true });
    +      expect(this.user.get('emailVerified')).toEqual(true);
    +    });
    +  });
    +
    +  describe('on password reset', function() {
    +    beforeEach(async function() {
    +      this.user = await Parse.User.logIn(
    +        'someemail@somedomain.com',
    +        'somepassword'
    +      );
    +    });
    +
    +    it('should not work with regex', async function() {
    +      expect(this.user.id).toEqual(this.objectId);
    +      await request({
    +        url: `${serverURL}/requestPasswordReset`,
    +        method: 'POST',
    +        headers,
    +        body: JSON.stringify({
    +          ...keys,
    +          _method: 'POST',
    +          email: 'someemail@somedomain.com',
    +        }),
    +      });
    +      await this.user.fetch({ useMasterKey: true });
    +      const passwordResetResponse = await request({
    +        url: `${serverURL}/apps/test/request_password_reset?username=someemail@somedomain.com&token[$regex]=`,
    +        method: 'GET',
    +      });
    +      expect(passwordResetResponse.status).toEqual(302);
    +      expect(passwordResetResponse.headers.location).toMatch(
    +        `\\/invalid\\_link\\.html`
    +      );
    +      await request({
    +        url: `${serverURL}/apps/test/request_password_reset`,
    +        method: 'POST',
    +        body: {
    +          token: { $regex: '' },
    +          username: 'someemail@somedomain.com',
    +          new_password: 'newpassword',
    +        },
    +      });
    +      try {
    +        await Parse.User.logIn('someemail@somedomain.com', 'newpassword');
    +        fail('should not work');
    +      } catch (e) {
    +        expect(e.code).toEqual(101);
    +        expect(e.message).toEqual('Invalid username/password.');
    +      }
    +    });
    +
    +    it('should work with plain token', async function() {
    +      expect(this.user.id).toEqual(this.objectId);
    +      await request({
    +        url: `${serverURL}/requestPasswordReset`,
    +        method: 'POST',
    +        headers,
    +        body: JSON.stringify({
    +          ...keys,
    +          _method: 'POST',
    +          email: 'someemail@somedomain.com',
    +        }),
    +      });
    +      await this.user.fetch({ useMasterKey: true });
    +      const token = this.user.get('_perishable_token');
    +      const passwordResetResponse = await request({
    +        url: `${serverURL}/apps/test/request_password_reset?username=someemail@somedomain.com&token=${token}`,
    +        method: 'GET',
    +      });
    +      expect(passwordResetResponse.status).toEqual(302);
    +      expect(passwordResetResponse.headers.location).toMatch(
    +        `\\/choose\\_password\\?token\\=${token}\\&`
    +      );
    +      await request({
    +        url: `${serverURL}/apps/test/request_password_reset`,
    +        method: 'POST',
    +        body: {
    +          token,
    +          username: 'someemail@somedomain.com',
    +          new_password: 'newpassword',
    +        },
    +      });
    +      const userAgain = await Parse.User.logIn(
    +        'someemail@somedomain.com',
    +        'newpassword'
    +      );
    +      expect(userAgain.id).toEqual(this.objectId);
    +    });
    +  });
    +});
    
  • src/middlewares.js+4 0 modified
    @@ -105,6 +105,10 @@ export function handleParseHeaders(req, res, next) {
         }
       }
     
    +  if (info.sessionToken && typeof info.sessionToken !== 'string') {
    +    info.sessionToken = info.sessionToken.toString();
    +  }
    +
       if (info.clientVersion) {
         info.clientSDK = ClientSDK.fromString(info.clientVersion);
       }
    
  • src/Routers/PublicAPIRouter.js+10 3 modified
    @@ -11,7 +11,10 @@ const views = path.resolve(__dirname, '../../views');
     
     export class PublicAPIRouter extends PromiseRouter {
       verifyEmail(req) {
    -    const { token, username } = req.query;
    +    const { username, token: rawToken } = req.query;
    +    const token =
    +      rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
    +
         const appId = req.params.appId;
         const config = Config.get(appId);
     
    @@ -122,7 +125,9 @@ export class PublicAPIRouter extends PromiseRouter {
           return this.missingPublicServerURL();
         }
     
    -    const { username, token } = req.query;
    +    const { username, token: rawToken } = req.query;
    +    const token =
    +      rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
     
         if (!username || !token) {
           return this.invalidLink(req);
    @@ -158,7 +163,9 @@ export class PublicAPIRouter extends PromiseRouter {
           return this.missingPublicServerURL();
         }
     
    -    const { username, token, new_password } = req.body;
    +    const { username, new_password, token: rawToken } = req.body;
    +    const token =
    +      rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
     
         if ((!username || !token || !new_password) && req.xhr === false) {
           return this.invalidLink(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

4

News mentions

0

No linked articles in our index yet.