CVE-2020-15270 (JavaScript, published 2020-10-22T22:15Z)
Description: value: Parse Server (npm package parse-server) broadcasts events to all clients without checking if the session token is valid. This allows clients with expired sessions to still receive subscription objects. It is not possible to create subscription objects with invalid session tokens. The issue is not patched.
Severity: MEDIUM (CVSS v3 base score: 4.3)
Weakness: CWE-672 \u2014 Operation on a Resource after Expiration or Release
The product uses, accesses, or otherwise operates on a resource after that resource has been expired, released, or revoked.
Repository: https://github.com/parse-community/parse-server
Files changed:
spec/ParseLiveQuery.spec.js
src/LiveQuery/ParseLiveQueryServer.js
src/Options/Definitions.js
src/Options/docs.js
src/Options/index.js
src/ParseServer.js
Commit message: Merge pull request from GHSA-2xm2-xj2q-qgpj

* Test case and fixes

* Change requestTimeout default to 5s

* Document new function argument

Vulnerable code:
  constructor(server: any, config: any = {}) {
    this.cacheController = getCacheController(config);
      maxAge: 60 * 60 * 1000, // 1h
          // Store a resolved promise with the error for 10 minutes
            60 * 10 * 1000
      "Number in milliseconds. When clients provide the sessionToken to the LiveQuery server, the LiveQuery server will try to fetch its ParseUser's objectId from parse server and store it in the cache. The value defines the duration of the cache. Check the following Security section and our protocol specification for details, defaults to 30 * 24 * 60 * 60 * 1000 ms (~30 days).",
 * @property {Number} cacheTimeout Number in milliseconds. When clients provide the sessionToken to the LiveQuery server, the LiveQuery server will try to fetch its ParseUser's objectId from parse server and store it in the cache. The value defines the duration of the cache. Check the following Security section and our protocol specification for details, defaults to 30 * 24 * 60 * 60 * 1000 ms (~30 days).
  /* Number in milliseconds. When clients provide the sessionToken to the LiveQuery server, the LiveQuery server will try to fetch its ParseUser's objectId from parse server and store it in the cache. The value defines the duration of the cache. Check the following Security section and our protocol specification for details, defaults to 30 * 24 * 60 * 60 * 1000 ms (~30 days).*/
        options.liveQueryServerOptions
   * @param {LiveQueryServerOptions} config options fot he liveQueryServer
  static createLiveQueryServer(httpServer, config: LiveQueryServerOptions) {
    return new ParseLiveQueryServer(httpServer, config);

Fixed code:
  it('should not broadcast event to client with invalid session token - avisory GHSA-2xm2-xj2q-qgpj', async done => {
    await reconfigureServer({
      liveQuery: {
        classNames: ['TestObject'],
      },
      liveQueryServerOptions: {
        cacheTimeout: 100,
      },
      startLiveQueryServer: true,
      verbose: false,
      silent: true,
      cacheTTL: 100,
    });
    const user = new Parse.User();
    user.setUsername('username');
    user.setPassword('password');
    await user.signUp();
    const obj1 = new Parse.Object('TestObject');
    const obj1ACL = new Parse.ACL();
    obj1ACL.setPublicReadAccess(false);
    obj1ACL.setReadAccess(user, true);
    obj1.setACL(obj1ACL);
    const obj2 = new Parse.Object('TestObject');
    const obj2ACL = new Parse.ACL();
    obj2ACL.setPublicReadAccess(false);
    obj2ACL.setReadAccess(user, true);
    obj2.setACL(obj2ACL);
    const query = new Parse.Query('TestObject');
    const subscription = await query.subscribe();
    subscription.on('create', obj => {
      if (obj.id !== obj1.id) {
        done.fail('should not fire');
      }
    });
    await obj1.save();
    await Parse.User.logOut();
    await new Promise(resolve => setTimeout(resolve, 200));
    await obj2.save();
    await new Promise(resolve => setTimeout(resolve, 200));
    done();
  });

  constructor(server: any, config: any = {}, parseServerConfig: any = {}) {
    this.config = config;
    this.cacheController = getCacheController(parseServerConfig);

    config.cacheTimeout = config.cacheTimeout || 5 * 1000; // 5s
      maxAge: config.cacheTimeout,
            this.config.cacheTimeout
      "Number in milliseconds. When clients provide the sessionToken to the LiveQuery server, the LiveQuery server will try to fetch its ParseUser's objectId from parse server and store it in the cache. The value defines the duration of the cache. Check the following Security section and our protocol specification for details, defaults to 5 * 1000 ms (5 seconds).",
 * @property {Number} cacheTimeout Number in milliseconds. When clients provide the sessionToken to the LiveQuery server, the LiveQuery server will try to fetch its ParseUser's objectId from parse server and store it in the cache. The value defines the duration of the cache. Check the following Security section and our protocol specification for details, defaults to 5 * 1000 ms (5 seconds).
  /* Number in milliseconds. When clients provide the sessionToken to the LiveQuery server, the LiveQuery server will try to fetch its ParseUser's objectId from parse server and store it in the cache. The value defines the duration of the cache. Check the following Security section and our protocol specification for details, defaults to 5 * 1000 ms (5 seconds).*/
        options.liveQueryServerOptions,
        options
   * @param {LiveQueryServerOptions} config options for the liveQueryServer
   * @param {ParseServerOptions} options options for the ParseServer
  static createLiveQueryServer(
    httpServer,
    config: LiveQueryServerOptions,
    options: ParseServerOptions
  ) {
    return new ParseLiveQueryServer(httpServer, config, options);