VYPR
Moderate severityNVD Advisory· Published Mar 24, 2026· Updated Mar 24, 2026

Parse Server: Session update endpoint allows overwriting server-generated session fields

CVE-2026-33527

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.57 and 9.6.0-alpha.48, an authenticated user can overwrite server-generated session fields such as expiresAt and createdWith when updating their own session via the REST API. This allows bypassing the server's configured session lifetime policy, making a session effectively permanent. This issue has been patched in versions 8.6.57 and 9.6.0-alpha.48.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
parse-servernpm
>= 9.0.0, < 9.6.0-alpha.489.6.0-alpha.48
parse-servernpm
< 8.6.578.6.57

Affected products

1

Patches

2
26b628c8fb3c

fix: Session update endpoint allows overwriting server-generated session fields ([GHSA-jc39-686j-wp6q](https://github.com/parse-community/parse-server/security/advisories/GHSA-jc39-686j-wp6q)) (#10264)

2 files changed · +141 0
  • spec/ParseSession.spec.js+137 0 modified
    @@ -256,4 +256,141 @@ describe('Parse.Session', () => {
         expect(newSession.createdWith.action).toBe('create');
         expect(newSession.createdWith.authProvider).toBeUndefined();
       });
    +
    +  it('should reject expiresAt when updating a session via PUT', async () => {
    +    const user = await Parse.User.signUp('sessionupdateuser1', 'password');
    +    const sessionToken = user.getSessionToken();
    +
    +    // Get the session objectId
    +    const sessionRes = await request({
    +      method: 'GET',
    +      url: 'http://localhost:8378/1/sessions/me',
    +      headers: {
    +        'X-Parse-Application-Id': 'test',
    +        'X-Parse-REST-API-Key': 'rest',
    +        'X-Parse-Session-Token': sessionToken,
    +      },
    +    });
    +    const sessionId = sessionRes.data.objectId;
    +    const originalExpiresAt = sessionRes.data.expiresAt;
    +
    +    // Attempt to overwrite expiresAt via PUT
    +    const updateRes = await request({
    +      method: 'PUT',
    +      url: `http://localhost:8378/1/sessions/${sessionId}`,
    +      headers: {
    +        'X-Parse-Application-Id': 'test',
    +        'X-Parse-REST-API-Key': 'rest',
    +        'X-Parse-Session-Token': sessionToken,
    +        'Content-Type': 'application/json',
    +      },
    +      body: {
    +        expiresAt: { __type: 'Date', iso: '2099-12-31T23:59:59.000Z' },
    +      },
    +    }).catch(e => e);
    +
    +    expect(updateRes.data.code).toBe(Parse.Error.INVALID_KEY_NAME);
    +
    +    // Verify expiresAt was not changed
    +    const verifyRes = await request({
    +      method: 'GET',
    +      url: 'http://localhost:8378/1/sessions/me',
    +      headers: {
    +        'X-Parse-Application-Id': 'test',
    +        'X-Parse-REST-API-Key': 'rest',
    +        'X-Parse-Session-Token': sessionToken,
    +      },
    +    });
    +    expect(verifyRes.data.expiresAt).toEqual(originalExpiresAt);
    +  });
    +
    +  it('should reject createdWith when updating a session via PUT', async () => {
    +    const user = await Parse.User.signUp('sessionupdateuser2', 'password');
    +    const sessionToken = user.getSessionToken();
    +
    +    // Get the session objectId
    +    const sessionRes = await request({
    +      method: 'GET',
    +      url: 'http://localhost:8378/1/sessions/me',
    +      headers: {
    +        'X-Parse-Application-Id': 'test',
    +        'X-Parse-REST-API-Key': 'rest',
    +        'X-Parse-Session-Token': sessionToken,
    +      },
    +    });
    +    const sessionId = sessionRes.data.objectId;
    +    const originalCreatedWith = sessionRes.data.createdWith;
    +
    +    // Attempt to overwrite createdWith via PUT
    +    const updateRes = await request({
    +      method: 'PUT',
    +      url: `http://localhost:8378/1/sessions/${sessionId}`,
    +      headers: {
    +        'X-Parse-Application-Id': 'test',
    +        'X-Parse-REST-API-Key': 'rest',
    +        'X-Parse-Session-Token': sessionToken,
    +        'Content-Type': 'application/json',
    +      },
    +      body: {
    +        createdWith: { action: 'attacker', authProvider: 'evil' },
    +      },
    +    }).catch(e => e);
    +
    +    expect(updateRes.data.code).toBe(Parse.Error.INVALID_KEY_NAME);
    +
    +    // Verify createdWith was not changed
    +    const verifyRes = await request({
    +      method: 'GET',
    +      url: 'http://localhost:8378/1/sessions/me',
    +      headers: {
    +        'X-Parse-Application-Id': 'test',
    +        'X-Parse-REST-API-Key': 'rest',
    +        'X-Parse-Session-Token': sessionToken,
    +      },
    +    });
    +    expect(verifyRes.data.createdWith).toEqual(originalCreatedWith);
    +  });
    +
    +  it('should allow master key to update expiresAt on a session', async () => {
    +    const user = await Parse.User.signUp('sessionupdateuser3', 'password');
    +    const sessionToken = user.getSessionToken();
    +
    +    // Get the session objectId
    +    const sessionRes = await request({
    +      method: 'GET',
    +      url: 'http://localhost:8378/1/sessions/me',
    +      headers: {
    +        'X-Parse-Application-Id': 'test',
    +        'X-Parse-REST-API-Key': 'rest',
    +        'X-Parse-Session-Token': sessionToken,
    +      },
    +    });
    +    const sessionId = sessionRes.data.objectId;
    +    const farFuture = '2099-12-31T23:59:59.000Z';
    +
    +    // Master key should be able to update expiresAt
    +    await request({
    +      method: 'PUT',
    +      url: `http://localhost:8378/1/sessions/${sessionId}`,
    +      headers: {
    +        'X-Parse-Application-Id': 'test',
    +        'X-Parse-Master-Key': 'test',
    +        'Content-Type': 'application/json',
    +      },
    +      body: {
    +        expiresAt: { __type: 'Date', iso: farFuture },
    +      },
    +    });
    +
    +    // Verify expiresAt was changed
    +    const verifyRes = await request({
    +      method: 'GET',
    +      url: `http://localhost:8378/1/sessions/${sessionId}`,
    +      headers: {
    +        'X-Parse-Application-Id': 'test',
    +        'X-Parse-Master-Key': 'test',
    +      },
    +    });
    +    expect(verifyRes.data.expiresAt.iso).toBe(farFuture);
    +  });
     });
    
  • src/RestWrite.js+4 0 modified
    @@ -1147,6 +1147,10 @@ RestWrite.prototype.handleSession = function () {
           throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
         } else if (this.data.sessionToken) {
           throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
    +    } else if (this.data.expiresAt && !this.auth.isMaster && !this.auth.isMaintenance) {
    +      throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
    +    } else if (this.data.createdWith && !this.auth.isMaster && !this.auth.isMaintenance) {
    +      throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
         }
         if (!this.auth.isMaster) {
           this.query = {
    
ea68fc0b22a6

fix: Session update endpoint allows overwriting server-generated session fields ([GHSA-jc39-686j-wp6q](https://github.com/parse-community/parse-server/security/advisories/GHSA-jc39-686j-wp6q)) (#10263)

2 files changed · +141 0
  • spec/ParseSession.spec.js+137 0 modified
    @@ -257,6 +257,143 @@ describe('Parse.Session', () => {
         expect(newSession.createdWith.authProvider).toBeUndefined();
       });
     
    +  it('should reject expiresAt when updating a session via PUT', async () => {
    +    const user = await Parse.User.signUp('sessionupdateuser1', 'password');
    +    const sessionToken = user.getSessionToken();
    +
    +    // Get the session objectId
    +    const sessionRes = await request({
    +      method: 'GET',
    +      url: 'http://localhost:8378/1/sessions/me',
    +      headers: {
    +        'X-Parse-Application-Id': 'test',
    +        'X-Parse-REST-API-Key': 'rest',
    +        'X-Parse-Session-Token': sessionToken,
    +      },
    +    });
    +    const sessionId = sessionRes.data.objectId;
    +    const originalExpiresAt = sessionRes.data.expiresAt;
    +
    +    // Attempt to overwrite expiresAt via PUT
    +    const updateRes = await request({
    +      method: 'PUT',
    +      url: `http://localhost:8378/1/sessions/${sessionId}`,
    +      headers: {
    +        'X-Parse-Application-Id': 'test',
    +        'X-Parse-REST-API-Key': 'rest',
    +        'X-Parse-Session-Token': sessionToken,
    +        'Content-Type': 'application/json',
    +      },
    +      body: {
    +        expiresAt: { __type: 'Date', iso: '2099-12-31T23:59:59.000Z' },
    +      },
    +    }).catch(e => e);
    +
    +    expect(updateRes.data.code).toBe(Parse.Error.INVALID_KEY_NAME);
    +
    +    // Verify expiresAt was not changed
    +    const verifyRes = await request({
    +      method: 'GET',
    +      url: 'http://localhost:8378/1/sessions/me',
    +      headers: {
    +        'X-Parse-Application-Id': 'test',
    +        'X-Parse-REST-API-Key': 'rest',
    +        'X-Parse-Session-Token': sessionToken,
    +      },
    +    });
    +    expect(verifyRes.data.expiresAt).toEqual(originalExpiresAt);
    +  });
    +
    +  it('should reject createdWith when updating a session via PUT', async () => {
    +    const user = await Parse.User.signUp('sessionupdateuser2', 'password');
    +    const sessionToken = user.getSessionToken();
    +
    +    // Get the session objectId
    +    const sessionRes = await request({
    +      method: 'GET',
    +      url: 'http://localhost:8378/1/sessions/me',
    +      headers: {
    +        'X-Parse-Application-Id': 'test',
    +        'X-Parse-REST-API-Key': 'rest',
    +        'X-Parse-Session-Token': sessionToken,
    +      },
    +    });
    +    const sessionId = sessionRes.data.objectId;
    +    const originalCreatedWith = sessionRes.data.createdWith;
    +
    +    // Attempt to overwrite createdWith via PUT
    +    const updateRes = await request({
    +      method: 'PUT',
    +      url: `http://localhost:8378/1/sessions/${sessionId}`,
    +      headers: {
    +        'X-Parse-Application-Id': 'test',
    +        'X-Parse-REST-API-Key': 'rest',
    +        'X-Parse-Session-Token': sessionToken,
    +        'Content-Type': 'application/json',
    +      },
    +      body: {
    +        createdWith: { action: 'attacker', authProvider: 'evil' },
    +      },
    +    }).catch(e => e);
    +
    +    expect(updateRes.data.code).toBe(Parse.Error.INVALID_KEY_NAME);
    +
    +    // Verify createdWith was not changed
    +    const verifyRes = await request({
    +      method: 'GET',
    +      url: 'http://localhost:8378/1/sessions/me',
    +      headers: {
    +        'X-Parse-Application-Id': 'test',
    +        'X-Parse-REST-API-Key': 'rest',
    +        'X-Parse-Session-Token': sessionToken,
    +      },
    +    });
    +    expect(verifyRes.data.createdWith).toEqual(originalCreatedWith);
    +  });
    +
    +  it('should allow master key to update expiresAt on a session', async () => {
    +    const user = await Parse.User.signUp('sessionupdateuser3', 'password');
    +    const sessionToken = user.getSessionToken();
    +
    +    // Get the session objectId
    +    const sessionRes = await request({
    +      method: 'GET',
    +      url: 'http://localhost:8378/1/sessions/me',
    +      headers: {
    +        'X-Parse-Application-Id': 'test',
    +        'X-Parse-REST-API-Key': 'rest',
    +        'X-Parse-Session-Token': sessionToken,
    +      },
    +    });
    +    const sessionId = sessionRes.data.objectId;
    +    const farFuture = '2099-12-31T23:59:59.000Z';
    +
    +    // Master key should be able to update expiresAt
    +    await request({
    +      method: 'PUT',
    +      url: `http://localhost:8378/1/sessions/${sessionId}`,
    +      headers: {
    +        'X-Parse-Application-Id': 'test',
    +        'X-Parse-Master-Key': 'test',
    +        'Content-Type': 'application/json',
    +      },
    +      body: {
    +        expiresAt: { __type: 'Date', iso: farFuture },
    +      },
    +    });
    +
    +    // Verify expiresAt was changed
    +    const verifyRes = await request({
    +      method: 'GET',
    +      url: `http://localhost:8378/1/sessions/${sessionId}`,
    +      headers: {
    +        'X-Parse-Application-Id': 'test',
    +        'X-Parse-Master-Key': 'test',
    +      },
    +    });
    +    expect(verifyRes.data.expiresAt.iso).toBe(farFuture);
    +  });
    +
       describe('PUT /sessions/me', () => {
         it('should return error with invalid session token', async () => {
           const response = await request({
    
  • src/RestWrite.js+4 0 modified
    @@ -1180,6 +1180,10 @@ RestWrite.prototype.handleSession = function () {
           throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
         } else if (this.data.sessionToken) {
           throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
    +    } else if (this.data.expiresAt && !this.auth.isMaster && !this.auth.isMaintenance) {
    +      throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
    +    } else if (this.data.createdWith && !this.auth.isMaster && !this.auth.isMaintenance) {
    +      throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
         }
         if (!this.auth.isMaster) {
           this.query = {
    

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.