CVE-2024-3110: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: A stored Cross-Site Scripting (XSS) vulnerability exists in the mintplex-labs/anything-llm application, affecting versions up to and including the latest before 1.0.0. The vulnerability arises from the application's failure to properly sanitize and validate user-supplied URLs before embedding them into the application UI as external links with custom icons. Specifically, the application does not prevent the inclusion of 'javascript:' protocol payloads in URLs, which can be exploited by a user with manager role to execute arbitrary JavaScript code in the context of another user's session. This flaw can be leveraged to steal the admin's authorization token by crafting malicious URLs that, when clicked by the admin, send the token to an attacker-controlled server. The attacker can then use this token to perform unauthorized actions, escalate privileges to admin, or directly take over the admin account. The vulnerability is triggered when the malicious link is opened in a new tab using either the CTRL + left mouse button click or the mouse scroll wheel click, or in some non-updated versions of modern browsers, by directly clicking on the link.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
Published: 2024-06-06T19:15Z
Repository: https://github.com/mintplex-labs/anything-llm
Language: JavaScript
Files changed: server/models/systemSettings.js, server/utils/http/index.js
Commit date: 2024-03-29 13:39:11 -0700
Commit message: security: patch footer icon self-xss from privledged user

Diff: diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js
index e4c0f9499..9809a716e 100644
--- a/server/models/systemSettings.js
+++ b/server/models/systemSettings.js
@@ -1,9 +1,10 @@
 process.env.NODE_ENV === "development"
   ? require("dotenv").config({ path: `.env.${process.env.NODE_ENV}` })
   : require("dotenv").config();
 
+const { isValidUrl } = require("../utils/http");
 const prisma = require("../utils/prisma");
 
 const SystemSettings = {
   protectedFields: ["multi_user_mode"],
   supportedFields: [
@@ -16,12 +17,14 @@ const SystemSettings = {
     "support_email",
   ],
   validations: {
     footer_data: (updates) => {
       try {
-        const array = JSON.parse(updates);
-        return JSON.stringify(array.slice(0, 3)); // max of 3 items in footer.
+        const array = JSON.parse(updates)
+          .filter((setting) => isValidUrl(setting.url))
+          .slice(0, 3); // max of 3 items in footer.
+        return JSON.stringify(array);
       } catch (e) {
         console.error(`Failed to run validation function on footer_data`);
         return JSON.stringify([]);
       }
     },
diff --git a/server/utils/http/index.js b/server/utils/http/index.js
index 084b09c7e..eedc33154 100644
--- a/server/utils/http/index.js
+++ b/server/utils/http/index.js
@@ -66,15 +66,25 @@ function safeJsonParse(jsonString, fallback = null) {
     return JSON.parse(jsonString);
   } catch {}
   return fallback;
 }
 
+function isValidUrl(urlString = "") {
+  try {
+    const url = new URL(urlString);
+    if (!["http:", "https:"].includes(url.protocol)) return false;
+    return true;
+  } catch (e) {}
+  return false;
+}
+
 module.exports = {
   reqBody,
   multiUserMode,
   queryParams,
   makeJWT,
   decodeJWT,
   userFromSession,
   parseAuthHeader,
   safeJsonParse,
+  isValidUrl,
 };

Vulnerable code:         const array = JSON.parse(updates);
        return JSON.stringify(array.slice(0, 3)); // max of 3 items in footer.
Fixed code: const { isValidUrl } = require("../utils/http");
        const array = JSON.parse(updates)
          .filter((setting) => isValidUrl(setting.url))
          .slice(0, 3); // max of 3 items in footer.
        return JSON.stringify(array);
function isValidUrl(urlString = "") {
  try {
    const url = new URL(urlString);
    if (!["http:", "https:"].includes(url.protocol)) return false;
    return true;
  } catch (e) {}
  return false;
}

  isValidUrl,