diff --git a/Middlewares/inputValidationMiddleware.js b/Middlewares/inputValidationMiddleware.js index 5571996..2bb2200 100644 --- a/Middlewares/inputValidationMiddleware.js +++ b/Middlewares/inputValidationMiddleware.js @@ -171,7 +171,8 @@ const inputValidationMiddleware = (req, res, next) => { if (req.body && typeof req.body === 'object') { const bodyStr = JSON.stringify(req.body); if (suspiciousPatterns.some(pattern => pattern.test(bodyStr))) { - logger.warn(`Suspicious input detected in request body from ${req.ip}: ${req.path}`); + logger.warn(`Suspicious input blocked in request body from ${req.ip}: ${req.path}`); + return res.status(400).json({ error: 'Input invalide détecté.' }); } } @@ -179,11 +180,11 @@ const inputValidationMiddleware = (req, res, next) => { if (req.query && typeof req.query === 'object') { const queryStr = JSON.stringify(req.query); if (suspiciousPatterns.some(pattern => pattern.test(queryStr))) { - logger.warn(`Suspicious input detected in query params from ${req.ip}: ${req.path}`); + logger.warn(`Suspicious input blocked in query params from ${req.ip}: ${req.path}`); + return res.status(400).json({ error: 'Input invalide détecté.' }); } } - // Continuer sans bloquer (logging only pour ne pas casser l'app) next(); } catch (error) { logger.error('Error in input validation middleware:', error); diff --git a/package.json b/package.json index c1c0cfb..1e085a1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cdn-app/insider-myaxrin-labs-dinawo", - "version": "1.2.2-beta", + "version": "1.2.3-beta", "description": "", "main": "server.js", "scripts": { diff --git a/public/js/dashboard.js b/public/js/dashboard.js index 2459df8..f932919 100644 --- a/public/js/dashboard.js +++ b/public/js/dashboard.js @@ -1,4 +1,10 @@ // Dashboard JavaScript - Version corrigée + +function escapeAttr(str) { + if (typeof str !== 'string') return ''; + return str.replace(/&/g, '&').replace(/'/g, ''').replace(/"/g, '"').replace(//g, '>'); +} + document.addEventListener('DOMContentLoaded', function() { // Initialisation générale initializeDashboard(); @@ -682,10 +688,10 @@ function createCollaborationModal(itemName, itemType, data) { ? data.activeUsers.map(user => `
- ${user.name} + onerror="this.src='${escapeAttr(getDefaultAvatar(user.name))}'">
@@ -696,8 +702,8 @@ function createCollaborationModal(itemName, itemType, data) {
- @@ -794,10 +800,10 @@ function searchCollabUser(username, itemName, itemType, modal) {
- - + `; list.appendChild(div); input.value = ''; diff --git a/routes/Dpanel/API/DeleteFile.js b/routes/Dpanel/API/DeleteFile.js index 8d5136e..a794f1b 100644 --- a/routes/Dpanel/API/DeleteFile.js +++ b/routes/Dpanel/API/DeleteFile.js @@ -156,19 +156,30 @@ router.post('/', authenticateToken, (req, res) => { return res.status(400).json({ message: 'User ID or filename missing for file deletion.' }); } - const userFolderPath = path.join('cdn-files', userId); + const userFolderPath = path.resolve('cdn-files', userId); function findAndDeleteFile(folderPath) { + const resolvedFolder = path.resolve(folderPath); + if (!resolvedFolder.startsWith(userFolderPath + path.sep) && resolvedFolder !== userFolderPath) { + return false; + } + const filesInFolder = fs.readdirSync(folderPath); for (const file of filesInFolder) { const filePath = path.join(folderPath, file); + const resolvedFilePath = path.resolve(filePath); - if (!filePath.startsWith(userFolderPath)) { + if (!resolvedFilePath.startsWith(userFolderPath + path.sep)) { return false; } - if (fs.statSync(filePath).isDirectory()) { + const stat = fs.lstatSync(filePath); + if (stat.isSymbolicLink()) { + continue; + } + + if (stat.isDirectory()) { const fileDeletedInSubfolder = findAndDeleteFile(filePath); if (fileDeletedInSubfolder) { return true; diff --git a/routes/Dpanel/API/DeleteFileFolder.js b/routes/Dpanel/API/DeleteFileFolder.js index 4195c3d..5dc7430 100644 --- a/routes/Dpanel/API/DeleteFileFolder.js +++ b/routes/Dpanel/API/DeleteFileFolder.js @@ -26,8 +26,25 @@ router.post('/:folderName', authMiddleware, (req, res) => { const userId = req.userData.name; const { filename } = req.body; - const userFolderPath = path.join('cdn-files', userId || ''); -const filePath = path.join(userFolderPath, req.params.folderName, filename || ''); + if (!userId || !filename) { + return res.status(400).json({ error: 'Paramètres manquants.' }); + } + + const userFolderPath = path.resolve('cdn-files', userId); + const filePath = path.resolve(userFolderPath, req.params.folderName, filename); + + if (!filePath.startsWith(userFolderPath + path.sep)) { + return res.status(403).json({ error: 'Accès non autorisé.' }); + } + + try { + const stat = fs.lstatSync(filePath); + if (stat.isSymbolicLink()) { + return res.status(403).json({ error: 'Accès non autorisé.' }); + } + } catch (e) { + return res.status(404).json({ error: 'Le fichier spécifié n\'existe pas.' }); + } if (!fs.existsSync(filePath)) { return res.status(404).json({ error: 'Le fichier spécifié n\'existe pas.' }); diff --git a/routes/Dpanel/API/RenameFile.js b/routes/Dpanel/API/RenameFile.js index 67df6c5..3a689ac 100644 --- a/routes/Dpanel/API/RenameFile.js +++ b/routes/Dpanel/API/RenameFile.js @@ -142,16 +142,21 @@ router.post('/', authMiddleware, async (req, res) => { return res.status(400).json('Both currentName and newName must be provided.'); } - const userDir = path.join('cdn-files', userId); - const currentPath = path.join(userDir, filePath, currentName); - const newPath = path.join(userDir, filePath, newName); + const userDir = path.resolve('cdn-files', userId); + const currentPath = path.resolve(userDir, filePath, currentName); + const newPath = path.resolve(userDir, filePath, newName); - if (!currentPath.startsWith(userDir) || !newPath.startsWith(userDir)) { + if (!currentPath.startsWith(userDir + path.sep) || !newPath.startsWith(userDir + path.sep)) { ErrorLogger.error('Unauthorized directory access attempt'); return res.status(403).json('Unauthorized directory access attempt'); } try { + const stat = await fs.promises.lstat(currentPath); + if (stat.isSymbolicLink()) { + return res.status(403).json('Unauthorized: symbolic links are not allowed'); + } + await fs.promises.rename(currentPath, newPath); const data = await fs.promises.readFile(path.join(__dirname, '../../../data', 'file_info.json'), 'utf8'); @@ -170,7 +175,7 @@ router.post('/', authMiddleware, async (req, res) => { await fs.promises.writeFile(path.join(__dirname, '../../../data', 'file_info.json'), JSON.stringify(fileInfo, null, 2), 'utf8'); } - res.status(200).json('Operation was successful.'); + res.status(200).json('Operation was successful.'); } catch (err) { console.error(err); return res.status(500).json('Error renaming the file.'); @@ -186,16 +191,21 @@ router.post('/:filePath*', authMiddleware, async (req, res) => { return res.status(400).json('Both currentName and newName must be provided.'); } - const userDir = path.join('cdn-files', userId); - const currentPath = path.join(userDir, filePath, currentName); - const newPath = path.join(userDir, filePath, newName); + const userDir = path.resolve('cdn-files', userId); + const currentPath = path.resolve(userDir, filePath, currentName); + const newPath = path.resolve(userDir, filePath, newName); - if (!currentPath.startsWith(userDir) || !newPath.startsWith(userDir)) { + if (!currentPath.startsWith(userDir + path.sep) || !newPath.startsWith(userDir + path.sep)) { ErrorLogger.error('Unauthorized directory access attempt'); return res.status(403).json('Unauthorized directory access attempt'); } try { + const stat = await fs.promises.lstat(currentPath); + if (stat.isSymbolicLink()) { + return res.status(403).json('Unauthorized: symbolic links are not allowed'); + } + await fs.promises.rename(currentPath, newPath); const data = await fs.promises.readFile(path.join(__dirname, '../../../data', 'file_info.json'), 'utf8'); @@ -214,7 +224,7 @@ router.post('/:filePath*', authMiddleware, async (req, res) => { await fs.promises.writeFile(path.join(__dirname, '../../../data', 'file_info.json'), JSON.stringify(fileInfo, null, 2), 'utf8'); } - res.status(200).json('Operation was successful.'); + res.status(200).json('Operation was successful.'); } catch (err) { console.error(err); return res.status(500).json('Error renaming the file.'); diff --git a/routes/attachments.js b/routes/attachments.js index 4d51d5f..9f41844 100644 --- a/routes/attachments.js +++ b/routes/attachments.js @@ -30,21 +30,37 @@ async function findFileInUserDir(userId, filename) { return findFileInDir(userDir, filename); } -async function findFileInDir(dir, filename) { +async function findFileInDir(dir, filename, baseDir) { + if (!baseDir) baseDir = dir; + const resolvedDir = path.resolve(dir); + const resolvedBase = path.resolve(baseDir); + + if (resolvedDir !== resolvedBase && !resolvedDir.startsWith(resolvedBase + path.sep)) { + return null; + } + let files; try { files = await fs.readdir(dir, { withFileTypes: true }); } catch (err) { - return null; // Directory does not exist + return null; } for (const file of files) { const filePath = path.join(dir, file.name); + if (file.isSymbolicLink()) { + continue; + } + if (file.name === filename && file.isFile()) { - return filePath; + const resolvedFile = path.resolve(filePath); + if (resolvedFile.startsWith(resolvedBase + path.sep)) { + return filePath; + } + return null; } else if (file.isDirectory()) { - const found = await findFileInDir(filePath, filename); + const found = await findFileInDir(filePath, filename, baseDir); if (found) { return found; }