CVE-2024-3028: Improper Input Validation (CWE-20)
value: mintplex-labs/anything-llm is vulnerable to improper input validation, allowing attackers to read and delete arbitrary files on the server. By manipulating the 'logo_filename' parameter in the 'system-preferences' API endpoint, an attacker can construct requests to read sensitive files or the application's '.env' file, and even delete files by setting the 'logo_filename' to the path of the target file and invoking the 'remove-logo' API endpoint. This vulnerability is due to the lack of proper sanitization of user-supplied input.
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
Published: 2024-04-16T00:15Z
Repository: https://github.com/mintplex-labs/anything-llm
Language: JavaScript
Files changed: server/utils/files/logo.js
Commit date: 2024-03-26 15:25:46 -0700
Commit message: norm path logo

Diff: diff --git a/server/utils/files/logo.js b/server/utils/files/logo.js
index eb4738b09..681084010 100644
--- a/server/utils/files/logo.js
+++ b/server/utils/files/logo.js
@@ -1,10 +1,11 @@
 const path = require("path");
 const fs = require("fs");
 const { getType } = require("mime");
 const { v4 } = require("uuid");
 const { SystemSettings } = require("../../models/systemSettings");
+const { normalizePath } = require(".");
 const LOGO_FILENAME = "anything-llm.png";
 
 function validFilename(newFilename = "") {
   return ![LOGO_FILENAME].includes(newFilename);
 }
@@ -19,11 +20,11 @@ async function determineLogoFilepath(defaultFilename = LOGO_FILENAME) {
     ? path.join(process.env.STORAGE_DIR, "assets")
     : path.join(__dirname, "../../storage/assets");
   const defaultFilepath = path.join(basePath, defaultFilename);
 
   if (currentLogoFilename && validFilename(currentLogoFilename)) {
-    customLogoPath = path.join(basePath, currentLogoFilename);
+    customLogoPath = path.join(basePath, normalizePath(currentLogoFilename));
     return fs.existsSync(customLogoPath) ? customLogoPath : defaultFilepath;
   }
 
   return defaultFilepath;
 }
@@ -50,25 +51,25 @@ function fetchLogo(logoPath) {
 
 async function renameLogoFile(originalFilename = null) {
   const extname = path.extname(originalFilename) || ".png";
   const newFilename = `${v4()}${extname}`;
   const originalFilepath = process.env.STORAGE_DIR
-    ? path.join(process.env.STORAGE_DIR, "assets", originalFilename)
-    : path.join(__dirname, `../../storage/assets/${originalFilename}`);
+    ? path.join(process.env.STORAGE_DIR, "assets", normalizePath(originalFilename))
+    : path.join(__dirname, `../../storage/assets`, normalizePath(originalFilename));
   const outputFilepath = process.env.STORAGE_DIR
-    ? path.join(process.env.STORAGE_DIR, "assets", newFilename)
-    : path.join(__dirname, `../../storage/assets/${newFilename}`);
+    ? path.join(process.env.STORAGE_DIR, "assets", normalizePath(newFilename))
+    : path.join(__dirname, `../../storage/assets`, normalizePath(newFilename));
 
   fs.renameSync(originalFilepath, outputFilepath);
   return newFilename;
 }
 
 async function removeCustomLogo(logoFilename = LOGO_FILENAME) {
   if (!logoFilename || !validFilename(logoFilename)) return false;
   const logoPath = process.env.STORAGE_DIR
-    ? path.join(process.env.STORAGE_DIR, `assets/${logoFilename}`)
-    : path.join(__dirname, `../../storage/assets/${logoFilename}`);
+    ? path.join(process.env.STORAGE_DIR, `assets`, normalizePath(logoFilename))
+    : path.join(__dirname, `../../storage/assets`, normalizePath(logoFilename));
   if (fs.existsSync(logoPath)) fs.unlinkSync(logoPath);
   return true;
 }
 
 module.exports = {

Vulnerable code:     customLogoPath = path.join(basePath, currentLogoFilename);
    ? path.join(process.env.STORAGE_DIR, "assets", originalFilename)
    : path.join(__dirname, `../../storage/assets/${originalFilename}`);
    ? path.join(process.env.STORAGE_DIR, "assets", newFilename)
    : path.join(__dirname, `../../storage/assets/${newFilename}`);
    ? path.join(process.env.STORAGE_DIR, `assets/${logoFilename}`)
    : path.join(__dirname, `../../storage/assets/${logoFilename}`);
Fixed code: const { normalizePath } = require(".");
    customLogoPath = path.join(basePath, normalizePath(currentLogoFilename));
    ? path.join(process.env.STORAGE_DIR, "assets", normalizePath(originalFilename))
    : path.join(__dirname, `../../storage/assets`, normalizePath(originalFilename));
    ? path.join(process.env.STORAGE_DIR, "assets", normalizePath(newFilename))
    : path.join(__dirname, `../../storage/assets`, normalizePath(newFilename));
    ? path.join(process.env.STORAGE_DIR, `assets`, normalizePath(logoFilename))
    : path.join(__dirname, `../../storage/assets`, normalizePath(logoFilename));