Security Fix usd-2022-0041: CWE-284 Improper Access Control.

Thanks to Christian Pöschl of usd AG and xet7 !
This commit is contained in:
Lauri Ojansivu 2025-10-10 23:19:58 +03:00
parent 6bbd622066
commit f6591d7820
2 changed files with 112 additions and 2 deletions

View file

@ -588,15 +588,37 @@ Users.deny({
});
// Custom MongoDB engine that enforces field restrictions
class SecureMongoDBEngine extends MongoDBEngine {
getSearchCursor(searchObject, options) {
// Always enforce field projection to prevent data leakage
const secureProjection = {
_id: 1,
username: 1,
'profile.fullname': 1,
'profile.avatarUrl': 1,
};
// Override any projection passed in options
const secureOptions = {
...options,
projection: secureProjection,
};
return super.getSearchCursor(searchObject, secureOptions);
}
}
// Search a user in the complete server database by its name, username or emails adress. This
// is used for instance to add a new user to a board.
UserSearchIndex = new Index({
collection: Users,
fields: ['username', 'profile.fullname', 'profile.avatarUrl'],
allowedFields: ['username', 'profile.fullname', 'profile.avatarUrl'],
engine: new MongoDBEngine({
allowedFields: ['username', 'profile.fullname', 'profile.avatarUrl', '_id'],
engine: new SecureMongoDBEngine({
fields: function (searchObject, options) {
return {
_id: 1,
username: 1,
'profile.fullname': 1,
'profile.avatarUrl': 1,
@ -2756,6 +2778,51 @@ if (Meteor.isServer) {
});
}
});
// Server-side method to sanitize user data for search results
Meteor.methods({
sanitizeUserForSearch(userData) {
check(userData, Object);
// Only allow safe fields for user search
const safeFields = {
_id: 1,
username: 1,
'profile.fullname': 1,
'profile.avatarUrl': 1,
'profile.initials': 1,
'emails.address': 1,
'emails.verified': 1,
authenticationMethod: 1,
isAdmin: 1,
loginDisabled: 1,
teams: 1,
orgs: 1,
};
const sanitized = {};
for (const field of Object.keys(safeFields)) {
if (userData[field] !== undefined) {
sanitized[field] = userData[field];
}
}
// Ensure sensitive fields are never included
delete sanitized.services;
delete sanitized.resume;
delete sanitized.email;
delete sanitized.createdAt;
delete sanitized.modifiedAt;
delete sanitized.sessionData;
delete sanitized.importUsernames;
if (process.env.DEBUG === 'true') {
console.log('Sanitized user data for search:', Object.keys(sanitized));
}
return sanitized;
}
});
}
export default Users;

View file

@ -49,6 +49,49 @@ Meteor.publish('user-authenticationMethod', function (match) {
return ret;
});
// Secure user search publication for board sharing
Meteor.publish('user-search', function (searchTerm) {
check(searchTerm, String);
// Only allow logged-in users to search for other users
if (!this.userId) {
return this.ready();
}
// Create a regex for case-insensitive search
const searchRegex = new RegExp(searchTerm, 'i');
// Search for users by username, fullname, or email
const ret = ReactiveCache.getUsers(
{
$or: [
{ username: searchRegex },
{ 'profile.fullname': searchRegex },
{ 'emails.address': searchRegex }
]
},
{
fields: {
_id: 1,
username: 1,
'profile.fullname': 1,
'profile.avatarUrl': 1,
'profile.initials': 1,
'emails.address': 1,
'emails.verified': 1,
authenticationMethod: 1,
isAdmin: 1,
loginDisabled: 1,
teams: 1,
orgs: 1,
},
},
true,
);
return ret;
});
// update last connection date and last connection average time (in seconds) for a user
// function UpdateLastConnectionDateAndLastConnectionAverageTime(lstUsers) {
// let lastConnectionAverageTime;