fix: Create CLP not enforced before user field validation on signup (#10268)

This commit is contained in:
Manuel
2026-03-21 14:04:54 +00:00
committed by GitHub
parent e8cc6763b1
commit a0530c251a
2 changed files with 122 additions and 0 deletions
+101
View File
@@ -2278,6 +2278,107 @@ describe('(GHSA-w54v-hf9p-8856) User enumeration via email verification endpoint
});
});
describe('(GHSA-4m9m-p9j9-5hjw) User enumeration via signup endpoint', () => {
async function updateCLP(permissions) {
const response = await fetch(Parse.serverURL + '/schemas/_User', {
method: 'PUT',
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Master-Key': Parse.masterKey,
'Content-Type': 'application/json',
},
body: JSON.stringify({ classLevelPermissions: permissions }),
});
const body = await response.json();
if (body.error) {
throw body;
}
}
it('does not reveal existing username when public create CLP is disabled', async () => {
const user = new Parse.User();
user.setUsername('existingUser');
user.setPassword('password123');
await user.signUp();
await Parse.User.logOut();
await updateCLP({
get: { '*': true },
find: { '*': true },
create: {},
update: { '*': true },
delete: { '*': true },
addField: {},
});
const response = await request({
url: 'http://localhost:8378/1/classes/_User',
method: 'POST',
body: { username: 'existingUser', password: 'otherpassword' },
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest',
'Content-Type': 'application/json',
},
}).catch(e => e);
expect(response.data.code).not.toBe(Parse.Error.USERNAME_TAKEN);
expect(response.data.error).not.toContain('Account already exists');
expect(response.data.code).toBe(Parse.Error.OPERATION_FORBIDDEN);
});
it('does not reveal existing email when public create CLP is disabled', async () => {
const user = new Parse.User();
user.setUsername('emailUser');
user.setPassword('password123');
user.setEmail('existing@example.com');
await user.signUp();
await Parse.User.logOut();
await updateCLP({
get: { '*': true },
find: { '*': true },
create: {},
update: { '*': true },
delete: { '*': true },
addField: {},
});
const response = await request({
url: 'http://localhost:8378/1/classes/_User',
method: 'POST',
body: { username: 'newUser', password: 'otherpassword', email: 'existing@example.com' },
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest',
'Content-Type': 'application/json',
},
}).catch(e => e);
expect(response.data.code).not.toBe(Parse.Error.EMAIL_TAKEN);
expect(response.data.error).not.toContain('Account already exists');
expect(response.data.code).toBe(Parse.Error.OPERATION_FORBIDDEN);
});
it('still returns username taken error when public create CLP is enabled', async () => {
const user = new Parse.User();
user.setUsername('existingUser');
user.setPassword('password123');
await user.signUp();
await Parse.User.logOut();
const response = await request({
url: 'http://localhost:8378/1/classes/_User',
method: 'POST',
body: { username: 'existingUser', password: 'otherpassword' },
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest',
'Content-Type': 'application/json',
},
}).catch(e => e);
expect(response.data.code).toBe(Parse.Error.USERNAME_TAKEN);
});
});
describe('(GHSA-c442-97qw-j6c6) SQL Injection via $regex query operator field name in PostgreSQL adapter', () => {
const headers = {
'Content-Type': 'application/json',
+21
View File
@@ -131,6 +131,9 @@ RestWrite.prototype.execute = function () {
this.validSchemaController = schemaController;
return this.setRequiredFieldsIfNeeded();
})
.then(() => {
return this.validateCreatePermission();
})
.then(() => {
return this.transformUser();
})
@@ -698,6 +701,24 @@ RestWrite.prototype.checkRestrictedFields = async function () {
}
};
// Validates the create class-level permission before transformUser runs.
// This prevents user enumeration (username/email existence) when public
// create is disabled on _User, because transformUser checks uniqueness
// before the CLP is enforced in runDatabaseOperation.
RestWrite.prototype.validateCreatePermission = async function () {
if (this.query || this.auth.isMaster || this.auth.isMaintenance) {
return;
}
if (!this.validSchemaController) {
return;
}
await this.validSchemaController.validatePermission(
this.className,
this.runOptions.acl || [],
'create'
);
};
// The non-third-party parts of User transformation
RestWrite.prototype.transformUser = async function () {
var promise = Promise.resolve();