CVE-2024-4286 (published 2024-05-26T23:15Z)

Mintplex-Labs' anything-llm application is vulnerable to improper neutralization of special elements used in an expression language statement, identified in the commit id `57984fa85c31988b2eff429adfc654c46e0c342a`. The vulnerability arises from the application's handling of user modifications by managers or admins, allowing for the modification of all existing attributes of the `user` database entity without proper checks or sanitization. This flaw can be exploited to delete user threads, denying users access to their previously submitted data, or to inject fake threads and/or chat history for social engineering attacks.


Weakness information:
cwe_id: CWE-917
cwe_name: Improper Neutralization of Special Elements used in an Expression Language Statement ('Expression Language Injection')
description: The product constructs all or part of an expression language (EL) statement in a framework such as a Java Server Page (JSP) using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended EL statement before it is executed.
extended_description: Frameworks such as Java Server Page (JSP) allow a developer to insert executable expressions within otherwise-static content. When the developer is not aware of the executable nature of these expressions and/or does not disable them, then if an attacker can inject expressions, this could lead to code execution or other unexpected behaviors.
         

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-04-26 16:46:04-07:00
msg: Strengthen field validations on user Updates (#1201)

* Strengthen field validations on user Updates

* update writables
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: user.js
change_type: ModificationType.MODIFY
diff: @@ -2,6 +2,23 @@ const prisma = require("../utils/prisma");
 const { EventLogs } = require("./eventLogs");
 
 const User = {
+  writable: [
+    // Used for generic updates so we can validate keys in request body
+    "username",
+    "password",
+    "pfpFilename",
+    "role",
+    "suspended",
+  ],
+  // validations for the above writable fields.
+  castColumnValue: function (key, value) {
+    switch (key) {
+      case "suspended":
+        return Number(Boolean(value));
+      default:
+        return String(value);
+    }
+  },
   create: async function ({ username, password, role = "default" }) {
     const passwordCheck = this.checkPasswordComplexity(password);
     if (!passwordCheck.checkedOK) {
@@ -42,13 +59,26 @@ const User = {
 
   update: async function (userId, updates = {}) {
     try {
+      if (!userId) throw new Error("No user id provided for update");
       const currentUser = await prisma.users.findUnique({
         where: { id: parseInt(userId) },
       });
-      if (!currentUser) {
-        return { success: false, error: "User not found" };
-      }
+      if (!currentUser) return { success: false, error: "User not found" };
+
+      // Removes non-writable fields for generic updates
+      // and force-casts to the proper type;
+      Object.entries(updates).forEach(([key, value]) => {
+        if (this.writable.includes(key)) {
+          updates[key] = this.castColumnValue(key, value);
+          return;
+        }
+        delete updates[key];
+      });
+
+      if (Object.keys(updates).length === 0)
+        return { success: false, error: "No valid updates applied." };
 
+      // Handle password specific updates
       if (updates.hasOwnProperty("password")) {
         const passwordCheck = this.checkPasswordComplexity(updates.password);
         if (!passwordCheck.checkedOK) {
@@ -78,6 +108,24 @@ const User = {
     }
   },
 
+  // Explicit direct update of user object.
+  // Only use this method when directly setting a key value
+  // that takes no user input for the keys being modified.
+  _update: async function (id = null, data = {}) {
+    if (!id) throw new Error("No user id provided for update");
+
+    try {
+      const user = await prisma.users.update({
+        where: { id },
+        data,
+      });
+      return { user, message: null };
+    } catch (error) {
+      console.error(error.message);
+      return { user: null, message: error.message };
+    }
+  },
+
   get: async function (clause = {}) {
     try {
       const user = await prisma.users.findFirst({ where: clause });

programming_language: TypeScript
filename: index.js
change_type: ModificationType.MODIFY
diff: @@ -296,7 +296,7 @@ class OpenRouterLLM {
           try {
             JSON.parse(message);
             validJSON = true;
-          } catch { }
+          } catch {}
 
           if (!validJSON) {
             // It can be possible that the chunk decoding is running away

programming_language: TypeScript
filename: index.js
change_type: ModificationType.MODIFY
diff: @@ -22,7 +22,7 @@ async function generateRecoveryCodes(userId) {
   const { error } = await RecoveryCode.createMany(newRecoveryCodes);
   if (!!error) throw new Error(error);
 
-  const { success } = await User.update(userId, {
+  const { user: success } = await User._update(userId, {
     seen_recovery_codes: true,
   });
   if (!success) throw new Error("Failed to generate user recovery codes!");
@@ -80,6 +80,11 @@ async function resetPassword(token, _newPassword = "", confirmPassword = "") {
   // JOI password rules will be enforced inside .update.
   const { error } = await User.update(resetToken.user_id, {
     password: newPassword,
+  });
+
+  // seen_recovery_codes is not publicly writable
+  // so we have to do direct update here
+  await User._update(resetToken.user_id, {
     seen_recovery_codes: false,
   });
 

programming_language: TypeScript