Security Fix 9: Attachment upload not scoped to card/board relationship.

Thanks to Joshua Rogers of joshua.hu, Twitter MegaManSec !
This commit is contained in:
Lauri Ojansivu 2025-12-29 17:06:22 +02:00
parent 6dfa3beb2b
commit 1d16955b6d

View file

@ -99,6 +99,20 @@ if (Meteor.isServer) {
return sendErrorResponse(res, 404, 'Board not found');
}
// Verify that the card belongs to the specified board
if (card.boardId !== boardId) {
return sendErrorResponse(res, 400, 'Card does not belong to the specified board');
}
// Verify that the swimlaneId and listId match the card's actual swimlane and list
if (card.swimlaneId !== swimlaneId) {
return sendErrorResponse(res, 400, 'Swimlane ID does not match the card\'s swimlane');
}
if (card.listId !== listId) {
return sendErrorResponse(res, 400, 'List ID does not match the card\'s list');
}
// Check permissions
if (!board.isBoardMember(userId)) {
return sendErrorResponse(res, 403, 'You do not have permission to modify this card');
@ -270,6 +284,14 @@ if (Meteor.isServer) {
return sendErrorResponse(res, 403, 'You do not have permission to access this board');
}
// If cardId is provided, verify it belongs to the board
if (cardId && cardId !== 'null') {
const card = ReactiveCache.getCard(cardId);
if (!card || card.boardId !== boardId) {
return sendErrorResponse(res, 404, 'Card not found or does not belong to the specified board');
}
}
let query = { 'meta.boardId': boardId };
if (swimlaneId && swimlaneId !== 'null') {
@ -366,6 +388,25 @@ if (Meteor.isServer) {
return sendErrorResponse(res, 403, 'You do not have permission to modify the target card');
}
// Verify that the target card belongs to the target board
const targetCard = ReactiveCache.getCard(targetCardId);
if (!targetCard) {
return sendErrorResponse(res, 404, 'Target card not found');
}
if (targetCard.boardId !== targetBoardId) {
return sendErrorResponse(res, 400, 'Target card does not belong to the specified board');
}
// Verify that the target swimlaneId and listId match the card's actual swimlane and list
if (targetCard.swimlaneId !== targetSwimlaneId) {
return sendErrorResponse(res, 400, 'Target swimlane ID does not match the card\'s swimlane');
}
if (targetCard.listId !== targetListId) {
return sendErrorResponse(res, 400, 'Target list ID does not match the card\'s list');
}
// Check if target board allows attachments
if (!targetBoard.allowsAttachments) {
return sendErrorResponse(res, 403, 'Attachments are not allowed on the target board');
@ -503,6 +544,25 @@ if (Meteor.isServer) {
return sendErrorResponse(res, 403, 'You do not have permission to modify the target card');
}
// Verify that the target card belongs to the target board
const targetCard = ReactiveCache.getCard(targetCardId);
if (!targetCard) {
return sendErrorResponse(res, 404, 'Target card not found');
}
if (targetCard.boardId !== targetBoardId) {
return sendErrorResponse(res, 400, 'Target card does not belong to the specified board');
}
// Verify that the target swimlaneId and listId match the card's actual swimlane and list
if (targetCard.swimlaneId !== targetSwimlaneId) {
return sendErrorResponse(res, 400, 'Target swimlane ID does not match the card\'s swimlane');
}
if (targetCard.listId !== targetListId) {
return sendErrorResponse(res, 400, 'Target list ID does not match the card\'s list');
}
// Check if target board allows attachments
if (!targetBoard.allowsAttachments) {
return sendErrorResponse(res, 403, 'Attachments are not allowed on the target board');