CVE-2024-3153: Uncontrolled Resource Consumption (CWE-400)
value: mintplex-labs/anything-llm is affected by an uncontrolled resource consumption vulnerability in its upload file endpoint, leading to a denial of service (DOS) condition. Specifically, the server can be shut down by sending an invalid upload request. An attacker with the ability to upload documents can exploit this vulnerability to cause a DOS condition by manipulating the upload request.
CWE description: The product does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.
Published: 2024-06-06T19:16Z
Repository: https://github.com/mintplex-labs/anything-llm
Language: JavaScript
Files changed: server/endpoints/api/document/index.js, server/endpoints/system.js, server/endpoints/workspaces.js, server/utils/files/multer.js
Commit date: 2024-04-01 12:06:47 -0700
Commit message: Handle no-mutli-part form data error (#1004)


Diff: diff --git a/server/endpoints/api/document/index.js b/server/endpoints/api/document/index.js
index e3ab050c6..158f4df68 100644
--- a/server/endpoints/api/document/index.js
+++ b/server/endpoints/api/document/index.js
@@ -1,17 +1,16 @@
 const { Telemetry } = require("../../../models/telemetry");
 const { validApiKey } = require("../../../utils/middleware/validApiKey");
-const { setupMulter } = require("../../../utils/files/multer");
+const { handleFileUpload } = require("../../../utils/files/multer");
 const {
   viewLocalFiles,
   findDocumentInDocuments,
   normalizePath,
 } = require("../../../utils/files");
 const { reqBody } = require("../../../utils/http");
 const { EventLogs } = require("../../../models/eventLogs");
 const { CollectorApi } = require("../../../utils/collectorApi");
-const { handleUploads } = setupMulter();
 const fs = require("fs");
 const path = require("path");
 const { Document } = require("../../../models/documents");
 const documentsPath =
   process.env.NODE_ENV === "development"
@@ -21,12 +20,11 @@ const documentsPath =
 function apiDocumentEndpoints(app) {
   if (!app) return;
 
   app.post(
     "/v1/document/upload",
-    [validApiKey],
-    handleUploads.single("file"),
+    [validApiKey, handleFileUpload],
     async (request, response) => {
       /*
     #swagger.tags = ['Documents']
     #swagger.description = 'Upload a new file to AnythingLLM to be parsed and prepared for embedding.'
     #swagger.requestBody = {
diff --git a/server/endpoints/system.js b/server/endpoints/system.js
index 2e0eda844..753825b85 100644
--- a/server/endpoints/system.js
+++ b/server/endpoints/system.js
@@ -10,17 +10,15 @@ const {
   makeJWT,
   userFromSession,
   multiUserMode,
   queryParams,
 } = require("../utils/http");
-const { setupLogoUploads, setupPfpUploads } = require("../utils/files/multer");
+const { handleAssetUpload, handlePfpUpload } = require("../utils/files/multer");
 const { v4 } = require("uuid");
 const { SystemSettings } = require("../models/systemSettings");
 const { User } = require("../models/user");
 const { validatedRequest } = require("../utils/middleware/validatedRequest");
-const { handleLogoUploads } = setupLogoUploads();
-const { handlePfpUploads } = setupPfpUploads();
 const fs = require("fs");
 const path = require("path");
 const {
   getDefaultFilename,
   determineLogoFilepath,
@@ -529,12 +527,11 @@ function systemEndpoints(app) {
     }
   );
 
   app.post(
     "/system/upload-pfp",
-    [validatedRequest, flexUserRoleValid([ROLES.all])],
-    handlePfpUploads.single("file"),
+    [validatedRequest, flexUserRoleValid([ROLES.all]), handlePfpUpload],
     async function (request, response) {
       try {
         const user = await userFromSession(request, response);
         const uploadedFileName = request.randomFileName;
         if (!uploadedFileName) {
@@ -603,14 +600,17 @@ function systemEndpoints(app) {
     }
   );
 
   app.post(
     "/system/upload-logo",
-    [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
-    handleLogoUploads.single("logo"),
+    [
+      validatedRequest,
+      flexUserRoleValid([ROLES.admin, ROLES.manager]),
+      handleAssetUpload,
+    ],
     async (request, response) => {
-      if (!request.file || !request.file.originalname) {
+      if (!request?.file || !request?.file.originalname) {
         return response.status(400).json({ message: "No logo file provided." });
       }
 
       if (!validFilename(request.file.originalname)) {
         return response.status(400).json({
diff --git a/server/endpoints/workspaces.js b/server/endpoints/workspaces.js
index d3715cd84..da9e2ad94 100644
--- a/server/endpoints/workspaces.js
+++ b/server/endpoints/workspaces.js
@@ -1,12 +1,15 @@
+const path = require("path");
+const fs = require("fs");
 const { reqBody, multiUserMode, userFromSession } = require("../utils/http");
+const { normalizePath } = require("../utils/files");
 const { Workspace } = require("../models/workspace");
 const { Document } = require("../models/documents");
 const { DocumentVectors } = require("../models/vectors");
 const { WorkspaceChats } = require("../models/workspaceChats");
 const { getVectorDbClass } = require("../utils/helpers");
-const { setupMulter } = require("../utils/files/multer");
+const { handleFileUpload, handlePfpUpload } = require("../utils/files/multer");
 const { validatedRequest } = require("../utils/middleware/validatedRequest");
 const { Telemetry } = require("../models/telemetry");
 const {
   flexUserRoleValid,
   ROLES,
@@ -16,16 +19,10 @@ const {
   WorkspaceSuggestedMessages,
 } = require("../models/workspacesSuggestedMessages");
 const { validWorkspaceSlug } = require("../utils/middleware/validWorkspace");
 const { convertToChatHistory } = require("../utils/helpers/chat/responses");
 const { CollectorApi } = require("../utils/collectorApi");
-const { handleUploads } = setupMulter();
-const { setupPfpUploads } = require("../utils/files/multer");
-const { normalizePath } = require("../utils/files");
-const { handlePfpUploads } = setupPfpUploads();
-const path = require("path");
-const fs = require("fs");
 const {
   determineWorkspacePfpFilepath,
   fetchPfp,
 } = require("../utils/files/pfp");
 
@@ -100,12 +97,15 @@ function workspaceEndpoints(app) {
     }
   );
 
   app.post(
     "/workspace/:slug/upload",
-    [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
-    handleUploads.single("file"),
+    [
+      validatedRequest,
+      flexUserRoleValid([ROLES.admin, ROLES.manager]),
+      handleFileUpload,
+    ],
     async function (request, response) {
       const Collector = new CollectorApi();
       const { originalname } = request.file;
       const processingOnline = await Collector.online();
 
@@ -477,12 +477,15 @@ function workspaceEndpoints(app) {
     }
   );
 
   app.post(
     "/workspace/:slug/upload-pfp",
-    [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
-    handlePfpUploads.single("file"),
+    [
+      validatedRequest,
+      flexUserRoleValid([ROLES.admin, ROLES.manager]),
+      handlePfpUpload,
+    ],
     async function (request, response) {
       try {
         const { slug } = request.params;
         const uploadedFileName = request.randomFileName;
         if (!uploadedFileName) {
diff --git a/server/utils/files/multer.js b/server/utils/files/multer.js
index 874e7278c..22f1217e3 100644
--- a/server/utils/files/multer.js
+++ b/server/utils/files/multer.js
@@ -1,73 +1,116 @@
 const multer = require("multer");
 const path = require("path");
 const fs = require("fs");
 const { v4 } = require("uuid");
 
-function setupMulter() {
-  // Handle File uploads for auto-uploading.
-  const storage = multer.diskStorage({
-    destination: function (_, __, cb) {
-      const uploadOutput =
-        process.env.NODE_ENV === "development"
-          ? path.resolve(__dirname, `../../../collector/hotdir`)
-          : path.resolve(process.env.STORAGE_DIR, `../../collector/hotdir`);
-      cb(null, uploadOutput);
-    },
-    filename: function (_, file, cb) {
-      file.originalname = Buffer.from(file.originalname, "latin1").toString(
-        "utf8"
-      );
-      cb(null, file.originalname);
-    },
-  });
+// Handle File uploads for auto-uploading.
+const fileUploadStorage = multer.diskStorage({
+  destination: function (_, __, cb) {
+    const uploadOutput =
+      process.env.NODE_ENV === "development"
+        ? path.resolve(__dirname, `../../../collector/hotdir`)
+        : path.resolve(process.env.STORAGE_DIR, `../../collector/hotdir`);
+    cb(null, uploadOutput);
+  },
+  filename: function (_, file, cb) {
+    file.originalname = Buffer.from(file.originalname, "latin1").toString(
+      "utf8"
+    );
+    cb(null, file.originalname);
+  },
+});
 
-  return { handleUploads: multer({ storage }) };
-}
+// Asset storage for logos
+const assetUploadStorage = multer.diskStorage({
+  destination: function (_, __, cb) {
+    const uploadOutput =
+      process.env.NODE_ENV === "development"
+        ? path.resolve(__dirname, `../../storage/assets`)
+        : path.resolve(process.env.STORAGE_DIR, "assets");
+    fs.mkdirSync(uploadOutput, { recursive: true });
+    return cb(null, uploadOutput);
+  },
+  filename: function (_, file, cb) {
+    file.originalname = Buffer.from(file.originalname, "latin1").toString(
+      "utf8"
+    );
+    cb(null, file.originalname);
+  },
+});
 
-function setupLogoUploads() {
-  // Handle Logo uploads.
-  const storage = multer.diskStorage({
-    destination: function (_, __, cb) {
-      const uploadOutput =
-        process.env.NODE_ENV === "development"
-          ? path.resolve(__dirname, `../../storage/assets`)
-          : path.resolve(process.env.STORAGE_DIR, "assets");
-      fs.mkdirSync(uploadOutput, { recursive: true });
-      return cb(null, uploadOutput);
-    },
-    filename: function (_, file, cb) {
-      file.originalname = Buffer.from(file.originalname, "latin1").toString(
-        "utf8"
-      );
-      cb(null, file.originalname);
-    },
-  });
+// Asset sub-storage manager for pfp icons.
+const pfpUploadStorage = multer.diskStorage({
+  destination: function (_, __, cb) {
+    const uploadOutput =
+      process.env.NODE_ENV === "development"
+        ? path.resolve(__dirname, `../../storage/assets/pfp`)
+        : path.resolve(process.env.STORAGE_DIR, "assets/pfp");
+    fs.mkdirSync(uploadOutput, { recursive: true });
+    return cb(null, uploadOutput);
+  },
+  filename: function (req, file, cb) {
+    const randomFileName = `${v4()}${path.extname(file.originalname)}`;
+    req.randomFileName = randomFileName;
+    cb(null, randomFileName);
+  },
+});
 
-  return { handleLogoUploads: multer({ storage }) };
+// Handle Generic file upload as documents
+function handleFileUpload(request, response, next) {
+  const upload = multer({ storage: fileUploadStorage }).single("file");
+  upload(request, response, function (err) {
+    if (err) {
+      response
+        .status(500)
+        .json({
+          success: false,
+          error: `Invalid file upload. ${err.message}`,
+        })
+        .end();
+      return;
+    }
+    next();
+  });
 }
 
-function setupPfpUploads() {
-  const storage = multer.diskStorage({
-    destination: function (_, __, cb) {
-      const uploadOutput =
-        process.env.NODE_ENV === "development"
-          ? path.resolve(__dirname, `../../storage/assets/pfp`)
-          : path.resolve(process.env.STORAGE_DIR, "assets/pfp");
-      fs.mkdirSync(uploadOutput, { recursive: true });
-      return cb(null, uploadOutput);
-    },
-    filename: function (req, file, cb) {
-      const randomFileName = `${v4()}${path.extname(file.originalname)}`;
-      req.randomFileName = randomFileName;
-      cb(null, randomFileName);
-    },
+// Handle logo asset uploads
+function handleAssetUpload(request, response, next) {
+  const upload = multer({ storage: assetUploadStorage }).single("logo");
+  upload(request, response, function (err) {
+    if (err) {
+      response
+        .status(500)
+        .json({
+          success: false,
+          error: `Invalid file upload. ${err.message}`,
+        })
+        .end();
+      return;
+    }
+    next();
   });
+}
 
-  return { handlePfpUploads: multer({ storage }) };
+// Handle PFP file upload as logos
+function handlePfpUpload(request, response, next) {
+  const upload = multer({ storage: pfpUploadStorage }).single("file");
+  upload(request, response, function (err) {
+    if (err) {
+      response
+        .status(500)
+        .json({
+          success: false,
+          error: `Invalid file upload. ${err.message}`,
+        })
+        .end();
+      return;
+    }
+    next();
+  });
 }
 
 module.exports = {
-  setupMulter,
-  setupLogoUploads,
-  setupPfpUploads,
+  handleFileUpload,
+  handleAssetUpload,
+  handlePfpUpload,
 };

Vulnerable code: const { setupMulter } = require("../../../utils/files/multer");
const { handleUploads } = setupMulter();
    [validApiKey],
    handleUploads.single("file"),
const { setupLogoUploads, setupPfpUploads } = require("../utils/files/multer");
const { handleLogoUploads } = setupLogoUploads();
const { handlePfpUploads } = setupPfpUploads();
    [validatedRequest, flexUserRoleValid([ROLES.all])],
    handlePfpUploads.single("file"),
    [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
    handleLogoUploads.single("logo"),
      if (!request.file || !request.file.originalname) {
const { setupMulter } = require("../utils/files/multer");
const { handleUploads } = setupMulter();
const { setupPfpUploads } = require("../utils/files/multer");
const { normalizePath } = require("../utils/files");
const { handlePfpUploads } = setupPfpUploads();
const path = require("path");
const fs = require("fs");
    [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
    handleUploads.single("file"),
    [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
    handlePfpUploads.single("file"),
function setupMulter() {
  // Handle File uploads for auto-uploading.
  const storage = multer.diskStorage({
    destination: function (_, __, cb) {
      const uploadOutput =
        process.env.NODE_ENV === "development"
          ? path.resolve(__dirname, `../../../collector/hotdir`)
          : path.resolve(process.env.STORAGE_DIR, `../../collector/hotdir`);
      cb(null, uploadOutput);
    },
    filename: function (_, file, cb) {
      file.originalname = Buffer.from(file.originalname, "latin1").toString(
        "utf8"
      );
      cb(null, file.originalname);
    },
  });
  return { handleUploads: multer({ storage }) };
}
function setupLogoUploads() {
  // Handle Logo uploads.
  const storage = multer.diskStorage({
    destination: function (_, __, cb) {
      const uploadOutput =
        process.env.NODE_ENV === "development"
          ? path.resolve(__dirname, `../../storage/assets`)
          : path.resolve(process.env.STORAGE_DIR, "assets");
      fs.mkdirSync(uploadOutput, { recursive: true });
      return cb(null, uploadOutput);
    },
    filename: function (_, file, cb) {
      file.originalname = Buffer.from(file.originalname, "latin1").toString(
        "utf8"
      );
      cb(null, file.originalname);
    },
  });
  return { handleLogoUploads: multer({ storage }) };
function setupPfpUploads() {
  const storage = multer.diskStorage({
    destination: function (_, __, cb) {
      const uploadOutput =
        process.env.NODE_ENV === "development"
          ? path.resolve(__dirname, `../../storage/assets/pfp`)
          : path.resolve(process.env.STORAGE_DIR, "assets/pfp");
      fs.mkdirSync(uploadOutput, { recursive: true });
      return cb(null, uploadOutput);
    },
    filename: function (req, file, cb) {
      const randomFileName = `${v4()}${path.extname(file.originalname)}`;
      req.randomFileName = randomFileName;
      cb(null, randomFileName);
    },
  return { handlePfpUploads: multer({ storage }) };
  setupMulter,
  setupLogoUploads,
  setupPfpUploads,
Fixed code: const { handleFileUpload } = require("../../../utils/files/multer");
    [validApiKey, handleFileUpload],
const { handleAssetUpload, handlePfpUpload } = require("../utils/files/multer");
    [validatedRequest, flexUserRoleValid([ROLES.all]), handlePfpUpload],
    [
      validatedRequest,
      flexUserRoleValid([ROLES.admin, ROLES.manager]),
      handleAssetUpload,
    ],
      if (!request?.file || !request?.file.originalname) {
const path = require("path");
const fs = require("fs");
const { normalizePath } = require("../utils/files");
const { handleFileUpload, handlePfpUpload } = require("../utils/files/multer");
    [
      validatedRequest,
      flexUserRoleValid([ROLES.admin, ROLES.manager]),
      handleFileUpload,
    ],
    [
      validatedRequest,
      flexUserRoleValid([ROLES.admin, ROLES.manager]),
      handlePfpUpload,
    ],
// Handle File uploads for auto-uploading.
const fileUploadStorage = multer.diskStorage({
  destination: function (_, __, cb) {
    const uploadOutput =
      process.env.NODE_ENV === "development"
        ? path.resolve(__dirname, `../../../collector/hotdir`)
        : path.resolve(process.env.STORAGE_DIR, `../../collector/hotdir`);
    cb(null, uploadOutput);
  },
  filename: function (_, file, cb) {
    file.originalname = Buffer.from(file.originalname, "latin1").toString(
      "utf8"
    );
    cb(null, file.originalname);
  },
});
// Asset storage for logos
const assetUploadStorage = multer.diskStorage({
  destination: function (_, __, cb) {
    const uploadOutput =
      process.env.NODE_ENV === "development"
        ? path.resolve(__dirname, `../../storage/assets`)
        : path.resolve(process.env.STORAGE_DIR, "assets");
    fs.mkdirSync(uploadOutput, { recursive: true });
    return cb(null, uploadOutput);
  },
  filename: function (_, file, cb) {
    file.originalname = Buffer.from(file.originalname, "latin1").toString(
      "utf8"
    );
    cb(null, file.originalname);
  },
});
// Asset sub-storage manager for pfp icons.
const pfpUploadStorage = multer.diskStorage({
  destination: function (_, __, cb) {
    const uploadOutput =
      process.env.NODE_ENV === "development"
        ? path.resolve(__dirname, `../../storage/assets/pfp`)
        : path.resolve(process.env.STORAGE_DIR, "assets/pfp");
    fs.mkdirSync(uploadOutput, { recursive: true });
    return cb(null, uploadOutput);
  },
  filename: function (req, file, cb) {
    const randomFileName = `${v4()}${path.extname(file.originalname)}`;
    req.randomFileName = randomFileName;
    cb(null, randomFileName);
  },
});
// Handle Generic file upload as documents
function handleFileUpload(request, response, next) {
  const upload = multer({ storage: fileUploadStorage }).single("file");
  upload(request, response, function (err) {
    if (err) {
      response
        .status(500)
        .json({
          success: false,
          error: `Invalid file upload. ${err.message}`,
        })
        .end();
      return;
    }
    next();
  });
// Handle logo asset uploads
function handleAssetUpload(request, response, next) {
  const upload = multer({ storage: assetUploadStorage }).single("logo");
  upload(request, response, function (err) {
    if (err) {
      response
        .status(500)
        .json({
          success: false,
          error: `Invalid file upload. ${err.message}`,
        })
        .end();
      return;
    }
    next();
}
// Handle PFP file upload as logos
function handlePfpUpload(request, response, next) {
  const upload = multer({ storage: pfpUploadStorage }).single("file");
  upload(request, response, function (err) {
    if (err) {
      response
        .status(500)
        .json({
          success: false,
          error: `Invalid file upload. ${err.message}`,
        })
        .end();
      return;
    }
    next();
  });
  handleFileUpload,
  handleAssetUpload,
  handlePfpUpload,