CVE-2024-3569: Uncontrolled Resource Consumption (CWE-400)
value: A Denial of Service (DoS) vulnerability exists in the mintplex-labs/anything-llm repository when the application is running in 'just me' mode with a password. An attacker can exploit this vulnerability by making a request to the endpoint using the [validatedRequest] middleware with a specially crafted 'Authorization:' header. This vulnerability leads to uncontrolled resource consumption, causing a DoS condition.
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-04-10T17:15Z
Repository: https://github.com/mintplex-labs/anything-llm
Language: Other
Files changed: frontend/src/components/Modals/Password/index.jsx, frontend/src/components/PrivateRoute/index.jsx, frontend/src/pages/Login/index.jsx, frontend/src/utils/paths.js, server/utils/middleware/validatedRequest.js
Commit date: 2024-03-26 16:47:25 -0700
Commit message: handle expired token being null and prevent constant rerender (#956)

* handle expired token being null and prevent constant rerender

* reset defaults
Diff: diff --git a/frontend/src/components/Modals/Password/index.jsx b/frontend/src/components/Modals/Password/index.jsx
index 00fefe4ed..d31b80fdf 100644
--- a/frontend/src/components/Modals/Password/index.jsx
+++ b/frontend/src/components/Modals/Password/index.jsx
@@ -32,11 +32,11 @@ export default function PasswordModal({ mode = "single" }) {
       </div>
     </div>
   );
 }
 
-export function usePasswordModal() {
+export function usePasswordModal(notry = false) {
   const [auth, setAuth] = useState({
     loading: true,
     requiresAuth: false,
     mode: "single",
   });
@@ -45,11 +45,11 @@ export function usePasswordModal() {
     async function checkAuthReq() {
       if (!window) return;
 
       // If the last validity check is still valid
       // we can skip the loading.
-      if (!System.needsAuthCheck()) {
+      if (!System.needsAuthCheck() && notry === false) {
         setAuth({
           loading: false,
           requiresAuth: false,
           mode: "multi",
         });
@@ -58,11 +58,11 @@ export function usePasswordModal() {
 
       const settings = await System.keys();
       if (settings?.MultiUserMode) {
         const currentToken = window.localStorage.getItem(AUTH_TOKEN);
         if (!!currentToken) {
-          const valid = await System.checkAuth(currentToken);
+          const valid = notry ? false : await System.checkAuth(currentToken);
           if (!valid) {
             setAuth({
               loading: false,
               requiresAuth: true,
               mode: "multi",
@@ -100,18 +100,20 @@ export function usePasswordModal() {
           return;
         }
 
         const currentToken = window.localStorage.getItem(AUTH_TOKEN);
         if (!!currentToken) {
-          const valid = await System.checkAuth(currentToken);
+          const valid = notry ? false : await System.checkAuth(currentToken);
           if (!valid) {
             setAuth({
               loading: false,
               requiresAuth: true,
               mode: "single",
             });
             window.localStorage.removeItem(AUTH_TOKEN);
+            window.localStorage.removeItem(AUTH_USER);
+            window.localStorage.removeItem(AUTH_TIMESTAMP);
             return;
           } else {
             setAuth({
               loading: false,
               requiresAuth: false,
diff --git a/frontend/src/components/PrivateRoute/index.jsx b/frontend/src/components/PrivateRoute/index.jsx
index 165141bbb..1b4c71fbd 100644
--- a/frontend/src/components/PrivateRoute/index.jsx
+++ b/frontend/src/components/PrivateRoute/index.jsx
@@ -134,8 +134,8 @@ export default function PrivateRoute({ Component }) {
   return isAuthd ? (
     <UserMenu>
       <Component />
     </UserMenu>
   ) : (
-    <Navigate to={paths.login()} />
+    <Navigate to={paths.login(true)} />
   );
 }
diff --git a/frontend/src/pages/Login/index.jsx b/frontend/src/pages/Login/index.jsx
index cf8ab2493..4e77a5c66 100644
--- a/frontend/src/pages/Login/index.jsx
+++ b/frontend/src/pages/Login/index.jsx
@@ -1,13 +1,15 @@
 import React from "react";
 import PasswordModal, { usePasswordModal } from "@/components/Modals/Password";
 import { FullScreenLoader } from "@/components/Preloader";
 import { Navigate } from "react-router-dom";
 import paths from "@/utils/paths";
+import useQuery from "@/hooks/useQuery";
 
 export default function Login() {
-  const { loading, requiresAuth, mode } = usePasswordModal();
+  const query = useQuery();
+  const { loading, requiresAuth, mode } = usePasswordModal(!!query.get("nt"));
   if (loading) return <FullScreenLoader />;
   if (requiresAuth === false) return <Navigate to={paths.home()} />;
 
   return <PasswordModal mode={mode} />;
 }
diff --git a/frontend/src/utils/paths.js b/frontend/src/utils/paths.js
index 0f42e2237..5625fafb9 100644
--- a/frontend/src/utils/paths.js
+++ b/frontend/src/utils/paths.js
@@ -2,12 +2,12 @@ import { API_BASE } from "./constants";
 
 export default {
   home: () => {
     return "/";
   },
-  login: () => {
-    return "/login";
+  login: (noTry = false) => {
+    return `/login${noTry ? "?nt=1" : ""}`;
   },
   onboarding: {
     home: () => {
       return "/onboarding";
     },
diff --git a/server/utils/middleware/validatedRequest.js b/server/utils/middleware/validatedRequest.js
index 6f3df26da..551090a07 100644
--- a/server/utils/middleware/validatedRequest.js
+++ b/server/utils/middleware/validatedRequest.js
@@ -36,13 +36,21 @@ async function validatedRequest(request, response, next) {
     return;
   }
 
   const bcrypt = require("bcrypt");
   const { p } = decodeJWT(token);
+
+  if (p === null) {
+    response.status(401).json({
+      error: "Token expired or failed validation.",
+    });
+    return;
+  }
+
   if (!bcrypt.compareSync(p, bcrypt.hashSync(process.env.AUTH_TOKEN, 10))) {
     response.status(401).json({
-      error: "Invalid auth token found.",
+      error: "Invalid auth credentials.",
     });
     return;
   }
 
   next();

Vulnerable code: export function usePasswordModal() {
      if (!System.needsAuthCheck()) {
          const valid = await System.checkAuth(currentToken);
          const valid = await System.checkAuth(currentToken);
    <Navigate to={paths.login()} />
  const { loading, requiresAuth, mode } = usePasswordModal();
  login: () => {
    return "/login";
      error: "Invalid auth token found.",
Fixed code: export function usePasswordModal(notry = false) {
      if (!System.needsAuthCheck() && notry === false) {
          const valid = notry ? false : await System.checkAuth(currentToken);
          const valid = notry ? false : await System.checkAuth(currentToken);
            window.localStorage.removeItem(AUTH_USER);
            window.localStorage.removeItem(AUTH_TIMESTAMP);
    <Navigate to={paths.login(true)} />
import useQuery from "@/hooks/useQuery";
  const query = useQuery();
  const { loading, requiresAuth, mode } = usePasswordModal(!!query.get("nt"));
  login: (noTry = false) => {
    return `/login${noTry ? "?nt=1" : ""}`;

  if (p === null) {
    response.status(401).json({
      error: "Token expired or failed validation.",
    });
    return;
  }

      error: "Invalid auth credentials.",