CVE-2024-3283 (published 2024-04-10T17:15Z)

A vulnerability in mintplex-labs/anything-llm allows users with manager roles to escalate their privileges to admin roles through a mass assignment issue. The '/admin/system-preferences' API endpoint improperly authorizes manager-level users to modify the 'multi_user_mode' system variable, enabling them to access the '/api/system/enable-multi-user' endpoint and create a new admin user. This issue results from the endpoint accepting a full JSON object in the request body without proper validation of modifiable fields, leading to unauthorized modification of system settings and subsequent privilege escalation.


Weakness information:
cwe_id: CWE-915
cwe_name: Improperly Controlled Modification of Dynamically-Determined Object Attributes
description: The product receives input from an upstream component that specifies multiple attributes, properties, or fields that are to be initialized or updated in an object, but it does not properly control which attributes can be modified.
extended_description: 
            If the object contains attributes that were only intended for internal use, then their unexpected modification could lead to a vulnerability.
            This weakness is sometimes known by the language-specific mechanisms that make it possible, such as mass assignment, autobinding, or object injection.
         
         

Fix information:
repo_url: https://github.com/mintplex-labs/anything-llm
commit_details: repo_url: https://github.com/mintplex-labs/anything-llm
author: Timothy Carambat
author_date: 2024-03-29 10:56:32-07:00
msg: Patch ability to update multi-user-flag once set (#993)

* Patch ability to update multi-user-flag once set

* update logo function to safe update key values
repository: repo_url: https://github.com/mintplex-labs/anything-llm
repo_name: Mintplex-Labs/anything-llm
description: The all-in-one Desktop & Docker AI application with full RAG and AI Agent capabilities.
repo_language: JavaScript
owner: mintplex-labs
file_changes: filename: system.js
change_type: ModificationType.MODIFY
diff: @@ -400,7 +400,7 @@ function systemEndpoints(app) {
           password,
           role: ROLES.admin,
         });
-        await SystemSettings.updateSettings({
+        await SystemSettings._updateSettings({
           multi_user_mode: true,
           users_can_delete_workspaces: false,
           limit_user_messages: false,
@@ -422,7 +422,7 @@ function systemEndpoints(app) {
         response.status(200).json({ success: !!user, error });
       } catch (e) {
         await User.delete({});
-        await SystemSettings.updateSettings({
+        await SystemSettings._updateSettings({
           multi_user_mode: false,
         });
 
@@ -623,7 +623,7 @@ function systemEndpoints(app) {
         const existingLogoFilename = await SystemSettings.currentLogoFilename();
         await removeCustomLogo(existingLogoFilename);
 
-        const { success, error } = await SystemSettings.updateSettings({
+        const { success, error } = await SystemSettings._updateSettings({
           logo_filename: newFilename,
         });
 
@@ -657,7 +657,7 @@ function systemEndpoints(app) {
       try {
         const currentLogoFilename = await SystemSettings.currentLogoFilename();
         await removeCustomLogo(currentLogoFilename);
-        const { success, error } = await SystemSettings.updateSettings({
+        const { success, error } = await SystemSettings._updateSettings({
           logo_filename: LOGO_FILENAME,
         });
 

programming_language: TypeScript
filename: systemSettings.js
change_type: ModificationType.MODIFY
diff: @@ -5,8 +5,8 @@ process.env.NODE_ENV === "development"
 const prisma = require("../utils/prisma");
 
 const SystemSettings = {
+  protectedFields: ["multi_user_mode"],
   supportedFields: [
-    "multi_user_mode",
     "users_can_delete_workspaces",
     "limit_user_messages",
     "message_limit",
@@ -287,26 +287,43 @@ const SystemSettings = {
     }
   },
 
+  // Can take generic keys and will pre-filter invalid keys
+  // from the set before sending to the explicit update function
+  // that will then enforce validations as well.
   updateSettings: async function (updates = {}) {
+    const validFields = Object.keys(updates).filter((key) =>
+      this.supportedFields.includes(key)
+    );
+
+    Object.entries(updates).forEach(([key]) => {
+      if (validFields.includes(key)) return;
+      delete updates[key];
+    });
+
+    return this._updateSettings(updates);
+  },
+
+  // Explicit update of settings + key validations.
+  // Only use this method when directly setting a key value
+  // that takes no user input for the keys being modified.
+  _updateSettings: async function (updates = {}) {
     try {
-      const updatePromises = Object.keys(updates)
-        .filter((key) => this.supportedFields.includes(key))
-        .map((key) => {
-          const validatedValue = this.validations.hasOwnProperty(key)
-            ? this.validations[key](updates[key])
-            : updates[key];
-
-          return prisma.system_settings.upsert({
-            where: { label: key },
-            update: {
-              value: validatedValue === null ? null : String(validatedValue),
-            },
-            create: {
-              label: key,
-              value: validatedValue === null ? null : String(validatedValue),
-            },
-          });
+      const updatePromises = Object.keys(updates).map((key) => {
+        const validatedValue = this.validations.hasOwnProperty(key)
+          ? this.validations[key](updates[key])
+          : updates[key];
+
+        return prisma.system_settings.upsert({
+          where: { label: key },
+          update: {
+            value: validatedValue === null ? null : String(validatedValue),
+          },
+          create: {
+            label: key,
+            value: validatedValue === null ? null : String(validatedValue),
+          },
         });
+      });
 
       await Promise.all(updatePromises);
       return { success: true, error: null };

programming_language: JavaScript
filename: telemetry.js
change_type: ModificationType.MODIFY
diff: @@ -67,7 +67,7 @@ const Telemetry = {
 
   setUid: async function () {
     const newId = v4();
-    await SystemSettings.updateSettings({ [this.label]: newId });
+    await SystemSettings._updateSettings({ [this.label]: newId });
     return newId;
   },
 

programming_language: TypeScript