CVE-2024-3025: Relative Path Traversal (CWE-23)
value: mintplex-labs/anything-llm is vulnerable to path traversal attacks due to insufficient validation of user-supplied input in the logo filename functionality. Attackers can exploit this vulnerability by manipulating the logo filename to reference files outside of the restricted directory. This can lead to unauthorized reading or deletion of files by utilizing the `/api/system/upload-logo` and `/api/system/logo` endpoints. The issue stems from the lack of filtering or validation on the logo filename, allowing attackers to target sensitive files such as the application's database.
CWE description: The product uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize sequences such as ".." that can resolve to a location that is outside of that directory.
Published: 2024-04-10T17: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));