CVE-2020-15126 (JavaScript, published 2020-07-22T23:15Z)
Description: value: In parser-server from version 3.5.0 and before 4.3.0, an authenticated user using the viewer GraphQL query can by pass all read security on his User object and can also by pass all objects linked via relation or Pointer on his User object.
Severity: MEDIUM (CVSS v3 base score: 6.5)
Weakness: CWE-863 \u2014 Incorrect Authorization
The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. This allows attackers to bypass intended access restrictions.
Repository: https://github.com/parse-community/parse-server
Files changed:
spec/ParseGraphQLServer.spec.js
src/GraphQL/loaders/usersMutations.js
src/GraphQL/loaders/usersQueries.js
Commit message: Merge pull request from GHSA-236h-rqv8-8q73

* Fix graphql viewer breach

* fix

* remove comment

Vulnerable code:
          use: (path) => {
          get: (path) => {
      await new Promise((resolve) =>
        httpServer.listen({ port: 13377 }, resolve)
      );
            return forward(operation).map((response) => {
        parseGraphQLServer._getGraphQLOptions = async (req) => {
          expect(fileType.fields.map((field) => field.name).sort()).toEqual([
          expect(classType.fields.map((field) => field.name).sort()).toEqual([
            readPreferenceType.enumValues.map((value) => value.name).sort()
          ).data['__schema'].types.map((type) => type.name);
            expectedTypes.every((type) => schemaTypes.indexOf(type) !== -1)
          ).data['__schema'].types.map((type) => type.name);
          ).data['__type'].fields.map((field) => field.name);
          ).data['__type'].fields.map((field) => field.name);
            .map((field) => field.name)
            .map((field) => field.name)
            .map((field) => field.name)
            .map((field) => field.name)
            .map((field) => field.name)
            .map((field) => field.name)
            .map((field) => field.name)
            .map((field) => field.name)
            .map((field) => field.name)
            .map((field) => field.name)
            .map((field) => field.name)
            .map((field) => field.name)
            .map((field) => field.name)
            .map((field) => field.name)
            .map((field) => field.name)
            .map((field) => field.name)
            .map((field) => field.name)
            .map((field) => field.name)
            .map((field) => field.name)
            .map((field) => field.name)
            .map((field) => field.name)
            .map((field) => field.name)
          ).data['__schema'].types.map((type) => type.name);
            expectedTypes.every((type) => schemaTypes.indexOf(type) !== -1)
          const possibleTypes = objectType.possibleTypes.map((o) => o.name);
          ).data['__type'].fields.map((field) => field.name);
          ).data['__type'].fields.map((field) => field.name);
            __type.inputFields.find((o) => o.name === 'price').type.kind
            __type.inputFields.find((o) => o.name === 'engine').type.kind
            __type.inputFields.find((o) => o.name === 'doors').type.kind
            __type2.fields.find((o) => o.name === 'price').type.kind
            __type2.fields.find((o) => o.name === 'engine').type.kind
            __type2.fields.find((o) => o.name === 'doors').type.kind
                    .map((value) => value.node.someField)
                    .map((value) => value.node.someField)
            const classes = Object.keys(result.data).map((fieldName) => ({
              .filter((schemaClass) => !schemaClass.name.startsWith('_'))
            findResult.data.classes.forEach((schemaClass) => {
                (o) => o.objectId === obj1.id
                (o) => o.objectId === obj2.id
                (elem) => elem.value
                .map((obj) =>
              objects.map(async (obj) =>
              objects.map(async (obj) =>
              objects.map(async (obj) =>
              [object1, object3, object4].map(async (obj) =>
              objects.slice(0, 3).map((obj) =>
              objects.slice(0, 2).map((obj) =>
          it('should respect protectedFields', async (done) => {
                  .forEach((call) => {
                .forEach((call) => {
                .forEach((call) => {
            result.data.customers.edges.forEach((resultObj) => {
                (object) => object.node.someField
                (object) => object.node.someField
                .map((object) => object.node.someField)
              ).data.find.edges.map((object) => object.node.someField)
                .map((object) => object.node.someField)
              ).data.find.edges.map((object) => object.node.someField)
                .map((object) => object.node.someField)
                .map((object) => object.node.someField)
              ).data.find.edges.map((object) => object.node.someField)
              ).data.find.edges.map((object) => object.node.someField)
                .map((object) => object.node.someField)
                .map((object) => object.node.someField)
              result.data.find.edges.map((obj) => obj.node.someField)
              result.data.someClasses.edges.map((edge) => edge.node.numberField)
              result.data.someClasses.edges.map((edge) => edge.node.numberField)
              result.data.someClasses.edges.map((edge) => edge.node.numberField)
              result.data.someClasses.edges.map((edge) => edge.node.numberField)
              result.data.someClasses.edges.map((edge) => edge.node.numberField)
                .forEach((call) => {
                .forEach((call) => {
                .forEach((call) => {
                  .forEach((call) => {
              result.data.graphQLClasses.edges.map((edge) => edge.node.objectId)
                  (edge) => edge.node.objectId
              objects.slice(0, 3).map(async (obj) => {
              objects.map(async (obj) => {
              objects.map(async (obj) => {
              objects.map(async (obj) => {
              [object1, object3, object4].map(async (obj) => {
              objects.slice(0, 3).map(async (obj) => {
              objects.slice(0, 2).map(async (obj) => {
              objects.slice(0, 3).map(async (obj) => {
              objects.map(async (obj) => {
              objects.map(async (obj) => {
              objects.map(async (obj) => {
              [object1, object3, object4].map(async (obj) => {
              objects.slice(0, 3).map(async (obj) => {
              objects.slice(0, 2).map(async (obj) => {
              objects.slice(0, 3).map(async (obj) => {
              objects.slice(0, 3).map(async (obj) => {
              objects.slice(0, 3).map(async (obj) => {
              objects.slice(0, 3).map(async (obj) => {
        it('should accept different params', (done) => {
          Parse.Cloud.define('hello', async (req) => {
              functionEnum.enumValues.map((value) => value.name).sort()
              functionEnum.enumValues.map((value) => value.name).sort()
                .map((call) => call.args[0])
              result.companies.edges.some((o) => o.node.objectId === company.id)
              result.companies.edges.some((o) => o.node.name === 'imACompany2')
              result.companies.edges.some((o) => o.node.name === 'imACompany3')
              (c) =>
                c.node.teams.edges.some((t) => t.node.name === 'imATeam2')
              (c) =>
                c.node.teams.edges.some((t) => t.node.name === 'imATeam3')
              result.companies.edges.some(
                (o) => o.node.objectId === company2.id
              )
              result.companies.edges.some((o) => o.node.name === 'imACompany3')
              result.companies.edges.some(
                (o) => o.node.objectId === company1.id
              )
              result.companies.edges.some((o) => o.node.name === 'imACompany2')
            result1.companies.edges.some((o) => o.node.objectId === company1.id)
            result1.companies.edges.some((o) => o.node.objectId === company2.id)
              edges.find((result) => result.node.id === create1.someClass.id)
                .node.someField
              edges.find((result) => result.node.id === create2.someClass.id)
                .node.someField
            expect(someField.map((element) => element.value)).toEqual(
            ].map((point) => ({
              somePolygonFieldValue.map((geoPoint) => ({
        await new Promise((resolve) =>
                resolve: (p) => p.name.toUpperCase(),
        await new Promise((resolve) =>
        await new Promise((resolve) =>
        const { sessionToken } = await objectsMutations.createObject(
        info.sessionToken = sessionToken;
            config,
            info,
            true
        const { sessionToken } = await objectsMutations.createObject(
        info.sessionToken = sessionToken;
            config,
            info,
            true
        const { sessionToken } = (
        info.sessionToken = sessionToken;
            config,
            info,
            true
          config,
          info,
          true
import Auth from '../../Auth';
  config,
  info,
  validatedToken
  if (validatedToken && !keys && !include) {
      .map(key => `user.${key}`)
      .map(included => `user.${included}`)
    Auth.master(config),
    '_Session',
    { sessionToken },
    info.context,
  if (
    !response.results ||
    response.results.length == 0 ||
    !response.results[0].user
  ) {
    const user = response.results[0].user;
          const { config, info } = context;
            config,
            info,

Fixed code:
          use: path => {
          get: path => {
      await new Promise(resolve => httpServer.listen({ port: 13377 }, resolve));
            return forward(operation).map(response => {
        parseGraphQLServer._getGraphQLOptions = async req => {
          expect(fileType.fields.map(field => field.name).sort()).toEqual([
          expect(classType.fields.map(field => field.name).sort()).toEqual([
            readPreferenceType.enumValues.map(value => value.name).sort()
          ).data['__schema'].types.map(type => type.name);
            expectedTypes.every(type => schemaTypes.indexOf(type) !== -1)
          ).data['__schema'].types.map(type => type.name);
          ).data['__type'].fields.map(field => field.name);
          ).data['__type'].fields.map(field => field.name);
            .map(field => field.name)
            .map(field => field.name)
            .map(field => field.name)
            .map(field => field.name)
            .map(field => field.name)
            .map(field => field.name)
            .map(field => field.name)
            .map(field => field.name)
            .map(field => field.name)
            .map(field => field.name)
            .map(field => field.name)
            .map(field => field.name)
            .map(field => field.name)
            .map(field => field.name)
            .map(field => field.name)
            .map(field => field.name)
            .map(field => field.name)
            .map(field => field.name)
            .map(field => field.name)
            .map(field => field.name)
            .map(field => field.name)
            .map(field => field.name)
          ).data['__schema'].types.map(type => type.name);
            expectedTypes.every(type => schemaTypes.indexOf(type) !== -1)
          const possibleTypes = objectType.possibleTypes.map(o => o.name);
          ).data['__type'].fields.map(field => field.name);
          ).data['__type'].fields.map(field => field.name);
            __type.inputFields.find(o => o.name === 'price').type.kind
            __type.inputFields.find(o => o.name === 'engine').type.kind
            __type.inputFields.find(o => o.name === 'doors').type.kind
            __type2.fields.find(o => o.name === 'price').type.kind
            __type2.fields.find(o => o.name === 'engine').type.kind
            __type2.fields.find(o => o.name === 'doors').type.kind
                    .map(value => value.node.someField)
                    .map(value => value.node.someField)
            const classes = Object.keys(result.data).map(fieldName => ({
              .filter(schemaClass => !schemaClass.name.startsWith('_'))
            findResult.data.classes.forEach(schemaClass => {
                o => o.objectId === obj1.id
                o => o.objectId === obj2.id
                elem => elem.value
                .map(obj =>
              objects.map(async obj =>
              objects.map(async obj =>
              objects.map(async obj =>
              [object1, object3, object4].map(async obj =>
              objects.slice(0, 3).map(obj =>
              objects.slice(0, 2).map(obj =>
          it('should respect protectedFields', async done => {
                  .forEach(call => {
                .forEach(call => {
                .forEach(call => {
            result.data.customers.edges.forEach(resultObj => {
                object => object.node.someField
                object => object.node.someField
                .map(object => object.node.someField)
              ).data.find.edges.map(object => object.node.someField)
                .map(object => object.node.someField)
              ).data.find.edges.map(object => object.node.someField)
                .map(object => object.node.someField)
                .map(object => object.node.someField)
              ).data.find.edges.map(object => object.node.someField)
              ).data.find.edges.map(object => object.node.someField)
                .map(object => object.node.someField)
                .map(object => object.node.someField)
              result.data.find.edges.map(obj => obj.node.someField)
              result.data.someClasses.edges.map(edge => edge.node.numberField)
              result.data.someClasses.edges.map(edge => edge.node.numberField)
              result.data.someClasses.edges.map(edge => edge.node.numberField)
              result.data.someClasses.edges.map(edge => edge.node.numberField)
              result.data.someClasses.edges.map(edge => edge.node.numberField)
                .forEach(call => {
                .forEach(call => {
                .forEach(call => {
                  .forEach(call => {
              result.data.graphQLClasses.edges.map(edge => edge.node.objectId)
                  edge => edge.node.objectId
              objects.slice(0, 3).map(async obj => {
              objects.map(async obj => {
              objects.map(async obj => {
              objects.map(async obj => {
              [object1, object3, object4].map(async obj => {
              objects.slice(0, 3).map(async obj => {
              objects.slice(0, 2).map(async obj => {
              objects.slice(0, 3).map(async obj => {
              objects.map(async obj => {
              objects.map(async obj => {
              objects.map(async obj => {
              [object1, object3, object4].map(async obj => {
              objects.slice(0, 3).map(async obj => {
              objects.slice(0, 2).map(async obj => {
              objects.slice(0, 3).map(async obj => {
              objects.slice(0, 3).map(async obj => {
              objects.slice(0, 3).map(async obj => {
              objects.slice(0, 3).map(async obj => {
        it('should return logged user and do not by pass pointer security', async () => {
          const masterKeyOnlyACL = new Parse.ACL();
          masterKeyOnlyACL.setPublicReadAccess(false);
          masterKeyOnlyACL.setPublicWriteAccess(false);
          const foo = new Parse.Object('Foo');
          foo.setACL(masterKeyOnlyACL);
          foo.set('bar', 'hello');
          await foo.save(null, { useMasterKey: true });
          const userName = 'userx1',
            password = 'user1',
            email = 'emailUserx1@parse.com';

          const user = new Parse.User();
          user.setUsername(userName);
          user.setPassword(password);
          user.setEmail(email);
          user.set('userFoo', foo);
          await user.signUp();

          await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();

          const session = await Parse.Session.current();
          const result = await apolloClient.query({
            query: gql`
              query GetCurrentUser {
                viewer {
                  sessionToken
                  user {
                    id
                    objectId
                    userFoo {
                      bar
                    }
                  }
                }
              }
            `,
            context: {
              headers: {
                'X-Parse-Session-Token': session.getSessionToken(),
              },
            },
          });

          const sessionToken = result.data.viewer.sessionToken;
          const { objectId, userFoo: resultFoo } = result.data.viewer.user;
          expect(objectId).toEqual(user.id);
          expect(sessionToken).toBeDefined();
          expect(resultFoo).toEqual(null);
        });
        it('should accept different params', done => {
          Parse.Cloud.define('hello', async req => {
              functionEnum.enumValues.map(value => value.name).sort()
              functionEnum.enumValues.map(value => value.name).sort()
                .map(call => call.args[0])
              result.companies.edges.some(o => o.node.objectId === company.id)
              result.companies.edges.some(o => o.node.name === 'imACompany2')
              result.companies.edges.some(o => o.node.name === 'imACompany3')
              c =>
                c.node.teams.edges.some(t => t.node.name === 'imATeam2')
              c =>
                c.node.teams.edges.some(t => t.node.name === 'imATeam3')
              result.companies.edges.some(o => o.node.objectId === company2.id)
              result.companies.edges.some(o => o.node.name === 'imACompany3')
              result.companies.edges.some(o => o.node.objectId === company1.id)
              result.companies.edges.some(o => o.node.name === 'imACompany2')
            result1.companies.edges.some(o => o.node.objectId === company1.id)
            result1.companies.edges.some(o => o.node.objectId === company2.id)
              edges.find(result => result.node.id === create1.someClass.id).node
                .someField
              edges.find(result => result.node.id === create2.someClass.id).node
                .someField
            expect(someField.map(element => element.value)).toEqual(
            ].map(point => ({
              somePolygonFieldValue.map(geoPoint => ({
        await new Promise(resolve =>
                resolve: p => p.name.toUpperCase(),
        await new Promise(resolve =>
        await new Promise(resolve =>
        const { sessionToken, objectId } = await objectsMutations.createObject(
        context.info.sessionToken = sessionToken;
            context,
            objectId
        const { sessionToken, objectId } = await objectsMutations.createObject(
        context.info.sessionToken = sessionToken;
            context,
            objectId
        const { sessionToken, objectId } = (
        context.info.sessionToken = sessionToken;
            context,
            objectId
          context,
          auth.user.id
import { Auth } from '../../Auth';
  context,
  userId
  const { info, config } = context;
  if (userId && !keys && !include) {
  if (userId) {
    // We need to re create the auth context
    // to avoid security breach if userId is provided
    context.auth = new Auth({
      config,
      isMaster: context.auth.isMaster,
      user: { id: userId },
    });
  }

      .map(key => `${key}`)
      .map(included => `${included}`)
    context.auth,
    '_User',
    // Get the user it self from auth object
    { objectId: context.auth.user.id },
    info.context
  if (!response.results || response.results.length == 0) {
    const user = response.results[0];
            context,