CVE-2024-0550: Relative Path Traversal (CWE-23)
value: A user who is privileged already `manager` or `admin` can set their profile picture via the frontend API using a relative filepath to then user the PFP GET API to download any valid files.

The attacker would have to have been granted privileged permissions to the system before executing this attack.
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-02-28T05:15Z
Repository: https://github.com/mintplex-labs/anything-llm
Language: JavaScript
Files changed: server/endpoints/system.js, server/utils/files/pfp.js
Commit date: 2024-01-14 16:53:44 -0800
Commit message: Normalize pfp path to prevent traversal

Diff: diff --git a/server/endpoints/system.js b/server/endpoints/system.js
index 6d985065..39b77a6a 100644
--- a/server/endpoints/system.js
+++ b/server/endpoints/system.js
@@ -500,11 +500,12 @@ function systemEndpoints(app) {
         if (!uploadedFileName) {
           return response.status(400).json({ message: "File upload failed." });
         }
 
         const userRecord = await User.get({ id: user.id });
-        const oldPfpFilename = userRecord.pfpFilename;
+        const oldPfpFilename = normalizePath(userRecord.pfpFilename);
+
         console.log("oldPfpFilename", oldPfpFilename);
         if (oldPfpFilename) {
           const oldPfpPath = path.join(
             __dirname,
             `../storage/assets/pfp/${oldPfpFilename}`
@@ -534,11 +535,11 @@ function systemEndpoints(app) {
     [validatedRequest, flexUserRoleValid],
     async function (request, response) {
       try {
         const user = await userFromSession(request, response);
         const userRecord = await User.get({ id: user.id });
-        const oldPfpFilename = userRecord.pfpFilename;
+        const oldPfpFilename = normalizePath(userRecord.pfpFilename);
         console.log("oldPfpFilename", oldPfpFilename);
         if (oldPfpFilename) {
           const oldPfpPath = path.join(
             __dirname,
             `../storage/assets/pfp/${oldPfpFilename}`
diff --git a/server/utils/files/pfp.js b/server/utils/files/pfp.js
index 943aa595..dd6ba0fe 100644
--- a/server/utils/files/pfp.js
+++ b/server/utils/files/pfp.js
@@ -1,9 +1,10 @@
 const path = require("path");
 const fs = require("fs");
 const { getType } = require("mime");
 const { User } = require("../../models/user");
+const { normalizePath } = require(".");
 
 function fetchPfp(pfpPath) {
   if (!fs.existsSync(pfpPath)) {
     return {
       found: false,
@@ -30,12 +31,11 @@ async function determinePfpFilepath(id) {
   if (!pfpFilename) return null;
 
   const basePath = process.env.STORAGE_DIR
     ? path.join(process.env.STORAGE_DIR, "assets/pfp")
     : path.join(__dirname, "../../storage/assets/pfp");
-  const pfpFilepath = path.join(basePath, pfpFilename);
-
+  const pfpFilepath = path.join(basePath, normalizePath(pfpFilename));
   if (!fs.existsSync(pfpFilepath)) return null;
   return pfpFilepath;
 }
 
 module.exports = {

Vulnerable code:         const oldPfpFilename = userRecord.pfpFilename;
        const oldPfpFilename = userRecord.pfpFilename;
  const pfpFilepath = path.join(basePath, pfpFilename);

Fixed code:         const oldPfpFilename = normalizePath(userRecord.pfpFilename);

        const oldPfpFilename = normalizePath(userRecord.pfpFilename);
const { normalizePath } = require(".");
  const pfpFilepath = path.join(basePath, normalizePath(pfpFilename));