CVE-2024-0436: Multiple Locks of a Critical Resource (CWE-764)
value: Theoretically, it would be possible for an attacker to brute-force the password for an instance in single-user password protection mode via a timing attack given the linear nature of the `!==` used for comparison.

The risk is minified by the additional overhead of the request, which varies in a non-constant nature making the attack less reliable to execute 
CWE description: The product locks a critical resource more times than intended, leading to an unexpected state in the system.
Published: 2024-02-26T16:27Z
Repository: https://github.com/mintplex-labs/anything-llm
Language: JavaScript
Files changed: frontend/src/components/Modals/Password/index.jsx, frontend/src/pages/Login/index.jsx, server/endpoints/system.js, server/utils/middleware/validatedRequest.js
Commit date: 2024-01-11 10:54:55 -0800
Commit message: Change pwd check to O(1) check to prevent timing attacks - single user mode (#575)

Change pwd check to O(1) check to prevent timing attacks
Diff: diff --git a/frontend/src/components/Modals/Password/index.jsx b/frontend/src/components/Modals/Password/index.jsx
index e9898685..00fefe4e 100644
--- a/frontend/src/components/Modals/Password/index.jsx
+++ b/frontend/src/components/Modals/Password/index.jsx
@@ -35,11 +35,11 @@ export default function PasswordModal({ mode = "single" }) {
 }
 
 export function usePasswordModal() {
   const [auth, setAuth] = useState({
     loading: true,
-    required: false,
+    requiresAuth: false,
     mode: "single",
   });
 
   useEffect(() => {
     async function checkAuthReq() {
diff --git a/frontend/src/pages/Login/index.jsx b/frontend/src/pages/Login/index.jsx
index ec5950cb..cf8ab249 100644
--- a/frontend/src/pages/Login/index.jsx
+++ b/frontend/src/pages/Login/index.jsx
@@ -1,9 +1,13 @@
 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";
 
 export default function Login() {
-  const { loading, mode } = usePasswordModal();
+  const { loading, requiresAuth, mode } = usePasswordModal();
   if (loading) return <FullScreenLoader />;
+  if (requiresAuth === false) return <Navigate to={paths.home()} />;
+
   return <PasswordModal mode={mode} />;
 }
diff --git a/server/endpoints/system.js b/server/endpoints/system.js
index a6acf47e..d2a13d10 100644
--- a/server/endpoints/system.js
+++ b/server/endpoints/system.js
@@ -105,10 +105,12 @@ function systemEndpoints(app) {
     }
   );
 
   app.post("/request-token", async (request, response) => {
     try {
+      const bcrypt = require("bcrypt");
+
       if (await SystemSettings.isMultiUserMode()) {
         const { username, password } = reqBody(request);
         const existingUser = await User.get({ username });
 
         if (!existingUser) {
@@ -119,11 +121,10 @@ function systemEndpoints(app) {
             message: "[001] Invalid login credentials.",
           });
           return;
         }
 
-        const bcrypt = require("bcrypt");
         if (!bcrypt.compareSync(password, existingUser.password)) {
           response.status(200).json({
             user: null,
             valid: false,
             token: null,
@@ -157,11 +158,16 @@ function systemEndpoints(app) {
           message: null,
         });
         return;
       } else {
         const { password } = reqBody(request);
-        if (password !== process.env.AUTH_TOKEN) {
+        if (
+          !bcrypt.compareSync(
+            password,
+            bcrypt.hashSync(process.env.AUTH_TOKEN, 10)
+          )
+        ) {
           response.status(401).json({
             valid: false,
             token: null,
             message: "[003] Invalid password provided",
           });
diff --git a/server/utils/middleware/validatedRequest.js b/server/utils/middleware/validatedRequest.js
index 275522bb..6f3df26d 100644
--- a/server/utils/middleware/validatedRequest.js
+++ b/server/utils/middleware/validatedRequest.js
@@ -34,12 +34,13 @@ async function validatedRequest(request, response, next) {
       error: "No auth token found.",
     });
     return;
   }
 
+  const bcrypt = require("bcrypt");
   const { p } = decodeJWT(token);
-  if (p !== process.env.AUTH_TOKEN) {
+  if (!bcrypt.compareSync(p, bcrypt.hashSync(process.env.AUTH_TOKEN, 10))) {
     response.status(401).json({
       error: "Invalid auth token found.",
     });
     return;
   }

Vulnerable code:     required: false,
  const { loading, mode } = usePasswordModal();
        const bcrypt = require("bcrypt");
        if (password !== process.env.AUTH_TOKEN) {
  if (p !== process.env.AUTH_TOKEN) {
Fixed code:     requiresAuth: false,
import { Navigate } from "react-router-dom";
import paths from "@/utils/paths";
  const { loading, requiresAuth, mode } = usePasswordModal();
  if (requiresAuth === false) return <Navigate to={paths.home()} />;

      const bcrypt = require("bcrypt");

        if (
          !bcrypt.compareSync(
            password,
            bcrypt.hashSync(process.env.AUTH_TOKEN, 10)
          )
        ) {
  const bcrypt = require("bcrypt");
  if (!bcrypt.compareSync(p, bcrypt.hashSync(process.env.AUTH_TOKEN, 10))) {