## Vulnerability Overview **Session Field Immutability Bypass Vulnerability** - Attackers can bypass field immutability protections by setting critical fields (`expiresAt`, `createdWith`, `installationId`, `sessionToken`) to `null` in PUT requests to update Sessions, thereby illegally modifying these attributes that should be read-only. ## Affected Scope - **Affected Versions**: Parse Server 9.7.1-alpha.4 and earlier - **Fixed Version**: 9.7.0-alpha.14 - **Vulnerability Type**: Logic Bypass / Access Control Bypass - **Related CVE**: [GHSA-f6j3-w8v3-cq22](https://github.com/parse-community/parse-server/security/advisories/GHSA-f6j3-w8v3-cq22) ## Fix Solution Add explicit `null` value checks in `RestWrite.js`, using the `in` operator to detect whether fields exist (including `null` values), rather than relying solely on truthiness checks: ```javascript // Before fix (vulnerable) } else if (this.data.expiresAt && this.auth.isMaster && this.auth.isMaintenance) { // After fix (secure) } else if ('expiresAt' in this.data && this.auth.isMaster && this.auth.isMaintenance) { ``` Similarly fix other fields: - `'installationId' in this.data` - `'sessionToken' in this.data` - `'expiresAt' in this.data` - `'createdWith' in this.data` ## POC Code ### Bypassing `expiresAt` Immutability ```javascript it('should reject null expiresAt when updating a session via PUT', async () => { const user = await Parse.User.signUp('sessionUpdateNull1', 'password'); const sessionToken = user.getSessionToken(); 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; 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: null, // Exploit null to bypass check }, }).catch(e => e); expect(updateRes.data.code).toBe(Parse.Error.INVALID_KEY_NAME); // Verify not modified const verifyRes = await request({...}); expect(verifyRes.data.expiresAt).toEqual(originalExpiresAt); }); ``` ### Bypassing `createdWith` Immutability ```javascript it('should reject null createdWith when updating a session via PUT', async () => { // ... same process ... body: { createdWith: null, // Exploit null to bypass check }, // ... }); ``` ### Bypassing `installationId` Immutability ```javascript it('should reject null installationId when updating a session via PUT', async () => { // ... same process ... body: { installationId: null, // Exploit null to bypass check }, // ... }); ``` ### Bypassing `sessionToken` Immutability ```javascript it('should reject null sessionToken when updating a session via PUT', async () => { // ... same process ... body: { sessionToken: null, // Exploit null to bypass check }, // ... }); ```