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