From 9720e703fd9432bf0e1bfea2358f8c7ea078f1b1 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Fri, 10 Oct 2025 22:59:20 +0300 Subject: [PATCH] Security Fix JVN#14269684: Broken access control. Thanks to Ryoya Koyama of Mitsui Bussan Secure Directions, Inc and xet7 ! --- models/attachments.js | 55 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/models/attachments.js b/models/attachments.js index c648def40..69e6cc83e 100644 --- a/models/attachments.js +++ b/models/attachments.js @@ -138,13 +138,40 @@ if (Meteor.isServer) { insert(userId, fileObj) { return allowIsBoardMember(userId, ReactiveCache.getBoard(fileObj.boardId)); }, - update(userId, fileObj) { + update(userId, fileObj, fields) { + // Only allow updates to specific fields that don't affect security + const allowedFields = ['name', 'size', 'type', 'extension', 'extensionWithDot', 'meta', 'versions']; + const isAllowedField = fields.every(field => allowedFields.includes(field)); + + if (!isAllowedField) { + if (process.env.DEBUG === 'true') { + console.warn('Blocked attempt to update restricted attachment fields:', fields); + } + return false; + } + return allowIsBoardMember(userId, ReactiveCache.getBoard(fileObj.boardId)); }, remove(userId, fileObj) { - return allowIsBoardMember(userId, ReactiveCache.getBoard(fileObj.boardId)); + // Additional security check: ensure the file belongs to the board the user has access to + if (!fileObj || !fileObj.boardId) { + if (process.env.DEBUG === 'true') { + console.warn('Blocked attachment removal: file has no boardId'); + } + return false; + } + + const board = ReactiveCache.getBoard(fileObj.boardId); + if (!board) { + if (process.env.DEBUG === 'true') { + console.warn('Blocked attachment removal: board not found'); + } + return false; + } + + return allowIsBoardMember(userId, board); }, - fetch: ['meta'], + fetch: ['meta', 'boardId'], }); Meteor.methods({ @@ -196,7 +223,29 @@ if (Meteor.isServer) { check(fileObjId, String); check(newName, String); + const currentUserId = Meteor.userId(); + if (!currentUserId) { + throw new Meteor.Error('not-authorized', 'User must be logged in'); + } + const fileObj = ReactiveCache.getAttachment(fileObjId); + if (!fileObj) { + throw new Meteor.Error('file-not-found', 'Attachment not found'); + } + + // Verify the user has permission to modify this attachment + const board = ReactiveCache.getBoard(fileObj.boardId); + if (!board) { + throw new Meteor.Error('board-not-found', 'Board not found'); + } + + if (!allowIsBoardMember(currentUserId, board)) { + if (process.env.DEBUG === 'true') { + console.warn(`Blocked unauthorized attachment rename attempt: user ${currentUserId} tried to rename attachment ${fileObjId} in board ${fileObj.boardId}`); + } + throw new Meteor.Error('not-authorized', 'You do not have permission to modify this attachment'); + } + rename(fileObj, newName, fileStoreStrategyFactory); }, validateAttachment(fileObjId) {