mirror of
https://github.com/DumbWareio/DumbDrop.git
synced 2025-10-23 07:41:58 +00:00
Fixed notifications config mapping and filename sanitation for cve/rce
add svg to login / index for favicon ensure file sanitization before and during notification
This commit is contained in:
@@ -20,6 +20,9 @@ services:
|
||||
- MAX_FILE_SIZE=1024
|
||||
- AUTO_UPLOAD=false
|
||||
- DUMBDROP_TITLE=DumbDrop-Dev
|
||||
# - APPRISE_URL=ntfy://dumbdrop-test
|
||||
# - APPRISE_MESSAGE=[DEV] New file uploaded - {filename} ({size}), Storage used {storage}
|
||||
# - APPRISE_SIZE_UNIT=auto
|
||||
command: npm run dev
|
||||
restart: unless-stopped
|
||||
# Enable container debugging if needed
|
||||
|
@@ -8,6 +8,7 @@
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/toastify-js"></script>
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link rel="icon" type="image/svg+xml" href="assets/icon.svg">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
@@ -5,6 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{SITE_TITLE}} - Login</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="icon" type="image/svg+xml" href="assets/icon.svg">
|
||||
<style>
|
||||
.login-container {
|
||||
display: flex;
|
||||
|
@@ -10,7 +10,7 @@ const crypto = require('crypto');
|
||||
const path = require('path');
|
||||
const { config } = require('../config');
|
||||
const logger = require('../utils/logger');
|
||||
const { getUniqueFilePath, getUniqueFolderPath } = require('../utils/fileUtils');
|
||||
const { getUniqueFilePath, getUniqueFolderPath, sanitizeFilename } = require('../utils/fileUtils');
|
||||
const { sendNotification } = require('../services/notifications');
|
||||
const fs = require('fs');
|
||||
const { cleanupIncompleteUploads } = require('../utils/cleanup');
|
||||
@@ -175,7 +175,8 @@ router.post('/init', async (req, res) => {
|
||||
batchActivity.set(batchId, Date.now());
|
||||
|
||||
// Sanitize filename and convert to forward slashes
|
||||
const safeFilename = path.normalize(filename)
|
||||
const sanitizedFilename = sanitizeFilename(filename);
|
||||
const safeFilename = path.normalize(sanitizedFilename)
|
||||
.replace(/^(\.\.(\/|\\|$))+/, '')
|
||||
.replace(/\\/g, '/')
|
||||
.replace(/^\/+/, ''); // Remove leading slashes
|
||||
@@ -264,7 +265,7 @@ router.post('/init', async (req, res) => {
|
||||
upload.writeStream.end();
|
||||
uploads.delete(uploadId);
|
||||
logger.success(`Completed zero-byte file upload: ${upload.safeFilename}`);
|
||||
await sendNotification(upload.safeFilename, 0, config);
|
||||
sendNotification(upload.safeFilename, 0, config);
|
||||
}
|
||||
|
||||
// Send response
|
||||
@@ -360,7 +361,7 @@ router.post('/chunk/:uploadId', express.raw({
|
||||
}
|
||||
|
||||
// Send notification
|
||||
await sendNotification(upload.safeFilename, upload.fileSize, config);
|
||||
sendNotification(upload.safeFilename, upload.fileSize, config);
|
||||
logUploadState('After Upload Complete');
|
||||
}
|
||||
|
||||
|
@@ -4,13 +4,10 @@
|
||||
* Handles message formatting and notification delivery.
|
||||
*/
|
||||
|
||||
const { exec } = require('child_process');
|
||||
const util = require('util');
|
||||
const { formatFileSize, calculateDirectorySize } = require('../utils/fileUtils');
|
||||
const { spawn } = require('child_process');
|
||||
const { formatFileSize, calculateDirectorySize, sanitizeFilename } = require('../utils/fileUtils');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
const execAsync = util.promisify(exec);
|
||||
|
||||
/**
|
||||
* Send a notification using Apprise
|
||||
* @param {string} filename - Name of uploaded file
|
||||
@@ -19,34 +16,56 @@ const execAsync = util.promisify(exec);
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function sendNotification(filename, fileSize, config) {
|
||||
const { APPRISE_URL, APPRISE_MESSAGE, APPRISE_SIZE_UNIT, uploadDir } = config;
|
||||
|
||||
if (!APPRISE_URL) {
|
||||
return;
|
||||
}
|
||||
const { appriseUrl, appriseMessage, appriseSizeUnit, uploadDir } = config;
|
||||
|
||||
try {
|
||||
const formattedSize = formatFileSize(fileSize, APPRISE_SIZE_UNIT);
|
||||
const dirSize = await calculateDirectorySize(uploadDir);
|
||||
const totalStorage = formatFileSize(dirSize);
|
||||
|
||||
// Sanitize the message components
|
||||
const sanitizedFilename = JSON.stringify(filename).slice(1, -1);
|
||||
const message = APPRISE_MESSAGE
|
||||
.replace('{filename}', sanitizedFilename)
|
||||
.replace('{size}', formattedSize)
|
||||
.replace('{storage}', totalStorage);
|
||||
console.debug("NOTIFICATIONS CONFIG:", filename, fileSize, config);
|
||||
if (!appriseUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use string command for better escaping
|
||||
const command = `apprise ${APPRISE_URL} -b "${message}"`;
|
||||
await execAsync(command, { shell: true });
|
||||
|
||||
logger.info(`Notification sent for: ${sanitizedFilename} (${formattedSize}, Total storage: ${totalStorage})`);
|
||||
} catch (err) {
|
||||
logger.error(`Failed to send notification: ${err.message}`);
|
||||
}
|
||||
try {
|
||||
const formattedSize = formatFileSize(fileSize, appriseSizeUnit);
|
||||
const dirSize = await calculateDirectorySize(uploadDir);
|
||||
const totalStorage = formatFileSize(dirSize);
|
||||
|
||||
// Sanitize the filename to remove any special characters that could cause issues
|
||||
const sanitizedFilename = sanitizeFilename(filename); // apply sanitization of filename again (in case)
|
||||
|
||||
// Construct the notification message by replacing placeholders
|
||||
const message = appriseMessage
|
||||
.replace('{filename}', sanitizedFilename)
|
||||
.replace('{size}', formattedSize)
|
||||
.replace('{storage}', totalStorage);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const appriseProcess = spawn('apprise', [appriseUrl, '-b', message]);
|
||||
|
||||
appriseProcess.stdout.on('data', (data) => {
|
||||
logger.info(`Apprise Output: ${data.toString().trim()}`);
|
||||
});
|
||||
|
||||
appriseProcess.stderr.on('data', (data) => {
|
||||
logger.error(`Apprise Error: ${data.toString().trim()}`);
|
||||
});
|
||||
|
||||
appriseProcess.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
logger.info(`Notification sent for: ${sanitizedFilename} (${formattedSize}, Total storage: ${totalStorage})`);
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`Apprise process exited with code ${code}`));
|
||||
}
|
||||
});
|
||||
|
||||
appriseProcess.on('error', (err) => {
|
||||
reject(new Error(`Apprise process failed to start: ${err.message}`));
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(`Failed to send notification: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sendNotification
|
||||
};
|
||||
sendNotification,
|
||||
};
|
@@ -160,10 +160,16 @@ async function getUniqueFolderPath(folderPath) {
|
||||
return finalPath;
|
||||
}
|
||||
|
||||
function sanitizeFilename(fileName) {
|
||||
const sanitized = fileName.replace(/[<>:"/\\|?*]+/g, '').replace(/["`$|;&<>]/g, '');
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
formatFileSize,
|
||||
calculateDirectorySize,
|
||||
ensureDirectoryExists,
|
||||
getUniqueFilePath,
|
||||
getUniqueFolderPath
|
||||
getUniqueFolderPath,
|
||||
sanitizeFilename
|
||||
};
|
Reference in New Issue
Block a user