mirror of
https://github.com/wekan/wekan.git
synced 2025-12-21 18:00:12 +01:00
Fix SECURITY ISSUE 5: Attachment API uses bearer value as userId and DoS (Low).
Thanks to Siam Thanat Hack (STH) and xet7 !
This commit is contained in:
parent
0a1a075f31
commit
ccd9034339
4 changed files with 312 additions and 11 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
import { Accounts } from 'meteor/accounts-base';
|
||||
import { WebApp } from 'meteor/webapp';
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { Attachments, fileStoreStrategyFactory } from '/models/attachments';
|
||||
|
|
@ -11,20 +12,24 @@ import { ObjectID } from 'bson';
|
|||
|
||||
// Attachment API HTTP routes
|
||||
if (Meteor.isServer) {
|
||||
// Helper function to authenticate API requests
|
||||
// Helper function to authenticate API requests using X-User-Id and X-Auth-Token
|
||||
function authenticateApiRequest(req) {
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
throw new Meteor.Error('unauthorized', 'Missing or invalid authorization header');
|
||||
const userId = req.headers['x-user-id'];
|
||||
const authToken = req.headers['x-auth-token'];
|
||||
|
||||
if (!userId || !authToken) {
|
||||
throw new Meteor.Error('unauthorized', 'Missing X-User-Id or X-Auth-Token headers');
|
||||
}
|
||||
|
||||
const token = authHeader.substring(7);
|
||||
// Here you would validate the token and get the user ID
|
||||
// For now, we'll use a simple approach - in production, you'd want proper JWT validation
|
||||
const userId = token; // This should be replaced with proper token validation
|
||||
|
||||
if (!userId) {
|
||||
throw new Meteor.Error('unauthorized', 'Invalid token');
|
||||
// Hash the token and validate against stored login tokens
|
||||
const hashedToken = Accounts._hashLoginToken(authToken);
|
||||
const user = Meteor.users.findOne({
|
||||
_id: userId,
|
||||
'services.resume.loginTokens.hashedToken': hashedToken,
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new Meteor.Error('unauthorized', 'Invalid credentials');
|
||||
}
|
||||
|
||||
return userId;
|
||||
|
|
@ -47,15 +52,33 @@ if (Meteor.isServer) {
|
|||
return next();
|
||||
}
|
||||
|
||||
// Set timeout to prevent hanging connections
|
||||
const timeout = setTimeout(() => {
|
||||
if (!res.headersSent) {
|
||||
sendErrorResponse(res, 408, 'Request timeout');
|
||||
}
|
||||
}, 30000); // 30 second timeout
|
||||
|
||||
try {
|
||||
const userId = authenticateApiRequest(req);
|
||||
|
||||
let body = '';
|
||||
let bodyComplete = false;
|
||||
|
||||
req.on('data', chunk => {
|
||||
body += chunk.toString();
|
||||
// Prevent excessive payload
|
||||
if (body.length > 50 * 1024 * 1024) { // 50MB limit
|
||||
req.connection.destroy();
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
});
|
||||
|
||||
req.on('end', () => {
|
||||
if (bodyComplete) return; // Already processed
|
||||
bodyComplete = true;
|
||||
clearTimeout(timeout);
|
||||
|
||||
try {
|
||||
const data = JSON.parse(body);
|
||||
const { boardId, swimlaneId, listId, cardId, fileData, fileName, fileType, storageBackend } = data;
|
||||
|
|
@ -154,7 +177,16 @@ if (Meteor.isServer) {
|
|||
sendErrorResponse(res, 500, error.message);
|
||||
}
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
clearTimeout(timeout);
|
||||
if (!res.headersSent) {
|
||||
console.error('Request error:', error);
|
||||
sendErrorResponse(res, 400, 'Request error');
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
clearTimeout(timeout);
|
||||
sendErrorResponse(res, 401, error.message);
|
||||
}
|
||||
});
|
||||
|
|
@ -287,15 +319,31 @@ if (Meteor.isServer) {
|
|||
return next();
|
||||
}
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
if (!res.headersSent) {
|
||||
sendErrorResponse(res, 408, 'Request timeout');
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
try {
|
||||
const userId = authenticateApiRequest(req);
|
||||
|
||||
let body = '';
|
||||
let bodyComplete = false;
|
||||
|
||||
req.on('data', chunk => {
|
||||
body += chunk.toString();
|
||||
if (body.length > 10 * 1024 * 1024) { // 10MB limit for metadata
|
||||
req.connection.destroy();
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
});
|
||||
|
||||
req.on('end', () => {
|
||||
if (bodyComplete) return;
|
||||
bodyComplete = true;
|
||||
clearTimeout(timeout);
|
||||
|
||||
try {
|
||||
const data = JSON.parse(body);
|
||||
const { attachmentId, targetBoardId, targetSwimlaneId, targetListId, targetCardId } = data;
|
||||
|
|
@ -388,7 +436,16 @@ if (Meteor.isServer) {
|
|||
sendErrorResponse(res, 500, error.message);
|
||||
}
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
clearTimeout(timeout);
|
||||
if (!res.headersSent) {
|
||||
console.error('Request error:', error);
|
||||
sendErrorResponse(res, 400, 'Request error');
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
clearTimeout(timeout);
|
||||
sendErrorResponse(res, 401, error.message);
|
||||
}
|
||||
});
|
||||
|
|
@ -399,15 +456,31 @@ if (Meteor.isServer) {
|
|||
return next();
|
||||
}
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
if (!res.headersSent) {
|
||||
sendErrorResponse(res, 408, 'Request timeout');
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
try {
|
||||
const userId = authenticateApiRequest(req);
|
||||
|
||||
let body = '';
|
||||
let bodyComplete = false;
|
||||
|
||||
req.on('data', chunk => {
|
||||
body += chunk.toString();
|
||||
if (body.length > 10 * 1024 * 1024) {
|
||||
req.connection.destroy();
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
});
|
||||
|
||||
req.on('end', () => {
|
||||
if (bodyComplete) return;
|
||||
bodyComplete = true;
|
||||
clearTimeout(timeout);
|
||||
|
||||
try {
|
||||
const data = JSON.parse(body);
|
||||
const { attachmentId, targetBoardId, targetSwimlaneId, targetListId, targetCardId } = data;
|
||||
|
|
@ -461,7 +534,16 @@ if (Meteor.isServer) {
|
|||
sendErrorResponse(res, 500, error.message);
|
||||
}
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
clearTimeout(timeout);
|
||||
if (!res.headersSent) {
|
||||
console.error('Request error:', error);
|
||||
sendErrorResponse(res, 400, 'Request error');
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
clearTimeout(timeout);
|
||||
sendErrorResponse(res, 401, error.message);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue