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:
gitmotion
2025-03-13 13:23:08 -07:00
parent 81baf87e93
commit e11c9261f7
6 changed files with 67 additions and 36 deletions

View File

@@ -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

View File

@@ -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">

View File

@@ -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;

View File

@@ -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');
}

View File

@@ -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,
};

View File

@@ -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
};