mirror of
https://github.com/RocketChat/Rocket.Chat.git
synced 2025-12-28 06:47:25 +00:00
fix: imported fixes 2025-12-19 (#37880)
Some checks failed
Code scanning - action / CodeQL-Build (push) Has been cancelled
Some checks failed
Code scanning - action / CodeQL-Build (push) Has been cancelled
Co-authored-by: Julio Araujo <julio.araujo@rocket.chat>
This commit is contained in:
parent
2356ad7124
commit
45b1c4e646
5
.changeset/fast-ligers-unite.md
Normal file
5
.changeset/fast-ligers-unite.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'@rocket.chat/meteor': patch
|
||||
---
|
||||
|
||||
Security Hotfix (https://docs.rocket.chat/docs/security-fixes-and-updates)
|
||||
@ -23,16 +23,22 @@ export const isValidQuery: {
|
||||
const verifyQuery = (query: Query, allowedAttributes: string[], allowedOperations: string[], parent = ''): boolean => {
|
||||
return Object.entries(removeDangerousProps(query)).every(([key, value]) => {
|
||||
const path = parent ? `${parent}.${key}` : key;
|
||||
if (parent === '' && path.startsWith('$')) {
|
||||
if (!allowedOperations.includes(path)) {
|
||||
isValidQuery.errors.push(`Invalid operation: ${path}`);
|
||||
if (key.startsWith('$')) {
|
||||
if (!allowedOperations.includes(key)) {
|
||||
isValidQuery.errors.push(`Invalid operation: ${key}`);
|
||||
return false;
|
||||
}
|
||||
if (!Array.isArray(value)) {
|
||||
isValidQuery.errors.push(`Invalid parameter for operation: ${path} : ${value}`);
|
||||
return false;
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value.every((v) => verifyQuery(v, allowedAttributes, allowedOperations));
|
||||
}
|
||||
return value.every((v) => verifyQuery(v, allowedAttributes, allowedOperations));
|
||||
|
||||
if (value instanceof Object) {
|
||||
return verifyQuery(value, allowedAttributes, allowedOperations, path);
|
||||
}
|
||||
|
||||
// handles primitive values (strings, numbers, booleans, etc.)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
|
||||
@ -51,7 +51,7 @@ import {
|
||||
} from '../../../lib/server/functions/checkUsernameAvailability';
|
||||
import { deleteUser } from '../../../lib/server/functions/deleteUser';
|
||||
import { getAvatarSuggestionForUser } from '../../../lib/server/functions/getAvatarSuggestionForUser';
|
||||
import { getFullUserDataByIdOrUsernameOrImportId } from '../../../lib/server/functions/getFullUserData';
|
||||
import { getFullUserDataByIdOrUsernameOrImportId, defaultFields, fullFields } from '../../../lib/server/functions/getFullUserData';
|
||||
import { generateUsernameSuggestion } from '../../../lib/server/functions/getUsernameSuggestion';
|
||||
import { saveCustomFields } from '../../../lib/server/functions/saveCustomFields';
|
||||
import { saveCustomFieldsWithoutValidation } from '../../../lib/server/functions/saveCustomFieldsWithoutValidation';
|
||||
@ -491,13 +491,18 @@ API.v1.addRoute(
|
||||
const { offset, count } = await getPaginationItems(this.queryParams);
|
||||
const { sort, fields, query } = await this.parseJsonQuery();
|
||||
|
||||
const nonEmptyQuery = getNonEmptyQuery(query, await hasPermissionAsync(this.userId, 'view-full-other-user-info'));
|
||||
const nonEmptyFields = getNonEmptyFields(fields);
|
||||
|
||||
const inclusiveFields = getInclusiveFields(nonEmptyFields);
|
||||
|
||||
const inclusiveFieldsKeys = Object.keys(inclusiveFields);
|
||||
|
||||
const hasUserQuery = query && Object.keys(query).length > 0;
|
||||
|
||||
const nonEmptyQuery = getNonEmptyQuery(query, await hasPermissionAsync(this.userId, 'view-full-other-user-info'));
|
||||
|
||||
// if user provided a query, validate it with their allowed operators
|
||||
// otherwise we use the default query (with $regex and $options)
|
||||
if (
|
||||
!isValidQuery(
|
||||
nonEmptyQuery,
|
||||
@ -509,7 +514,7 @@ API.v1.addRoute(
|
||||
inclusiveFieldsKeys.includes('type') && 'type.*',
|
||||
inclusiveFieldsKeys.includes('customFields') && 'customFields.*',
|
||||
].filter(Boolean) as string[],
|
||||
this.queryOperations,
|
||||
hasUserQuery ? this.queryOperations : [...this.queryOperations, '$regex', '$options'],
|
||||
)
|
||||
) {
|
||||
throw new Meteor.Error('error-invalid-query', isValidQuery.errors.join('\n'));
|
||||
@ -1117,8 +1122,13 @@ API.v1.addRoute(
|
||||
const selector: { exceptions: Required<IUser>['username'][]; conditions: Filter<IUser>; term: string } = JSON.parse(selectorRaw);
|
||||
|
||||
try {
|
||||
if (selector?.conditions && !isValidQuery(selector.conditions, ['*'], ['$or', '$and'])) {
|
||||
throw new Error('error-invalid-query');
|
||||
if (selector?.conditions) {
|
||||
const canViewFullInfo = await hasPermissionAsync(this.userId, 'view-full-other-user-info');
|
||||
const allowedFields = canViewFullInfo ? [...Object.keys(defaultFields), ...Object.keys(fullFields)] : Object.keys(defaultFields);
|
||||
|
||||
if (!isValidQuery(selector.conditions, allowedFields, ['$and', '$ne', '$exists'])) {
|
||||
throw new Error('error-invalid-query');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
return API.v1.failure(e);
|
||||
|
||||
@ -7,7 +7,7 @@ import { settings } from '../../../settings/server';
|
||||
|
||||
const logger = new Logger('getFullUserData');
|
||||
|
||||
const defaultFields = {
|
||||
export const defaultFields = {
|
||||
name: 1,
|
||||
username: 1,
|
||||
nickname: 1,
|
||||
@ -24,7 +24,7 @@ const defaultFields = {
|
||||
statusLivechat: 1,
|
||||
} as const;
|
||||
|
||||
const fullFields = {
|
||||
export const fullFields = {
|
||||
emails: 1,
|
||||
phone: 1,
|
||||
statusConnection: 1,
|
||||
|
||||
@ -174,7 +174,7 @@ describe('isValidQuery', () => {
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(isValidQuery(query, props, ['$or'])).to.be.true;
|
||||
expect(isValidQuery(query, props, ['$or', '$regex'])).to.be.true;
|
||||
expect(isValidQuery.errors.length).to.be.equals(0);
|
||||
});
|
||||
|
||||
@ -212,5 +212,15 @@ describe('isValidQuery', () => {
|
||||
),
|
||||
).to.be.false;
|
||||
});
|
||||
|
||||
it('should return false if the query contains nested conditions with disallowed operators', () => {
|
||||
const props = ['username'];
|
||||
const allowedOps = ['$and', '$ne'];
|
||||
const query = {
|
||||
$and: [{ username: { $exists: true } }, { username: { $ne: '1000' } }],
|
||||
};
|
||||
expect(isValidQuery(query, props, allowedOps)).to.be.false;
|
||||
expect(isValidQuery.errors.length).to.be.equals(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user