CVE-2024-1625: Incorrect Authorization (CWE-863)
value: An Insecure Direct Object Reference (IDOR) vulnerability exists in the lunary-ai/lunary application version 0.3.0, allowing unauthorized deletion of any organization's project. The vulnerability is due to insufficient authorization checks in the project deletion endpoint, where the endpoint fails to verify if the project ID provided in the request belongs to the requesting user's organization. As a result, an attacker can delete projects belonging to any organization by sending a crafted DELETE request with the target project's ID. This issue affects the project deletion functionality implemented in the projects.delete route.
CWE description: The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. This allows attackers to bypass intended access restrictions.
Published: 2024-04-10T17:15Z
Repository: https://github.com/lunary-ai/lunary
Language: TypeScript
Files changed: packages/backend/src/api/v1/projects/index.ts, packages/backend/src/utils/authorization.ts
Commit date: 2024-02-19 09:32:01 -0300
Commit message: fix: project deletion safeguard

Diff: diff --git a/packages/backend/src/api/v1/projects/index.ts b/packages/backend/src/api/v1/projects/index.ts
index 68535fd..584df16 100644
--- a/packages/backend/src/api/v1/projects/index.ts
+++ b/packages/backend/src/api/v1/projects/index.ts
@@ -1,5 +1,6 @@
+import { verifyProjectAccess } from "@/src/utils/authorization"
 import sql from "@/src/utils/db"
 import Context from "@/src/utils/koa"
 import Router from "koa-router"
 import { z } from "zod"
 
@@ -67,18 +68,31 @@ projects.post("/", async (ctx: Context) => {
   ctx.body = project
 })
 
 projects.delete("/:projectId", async (ctx: Context) => {
   const { projectId } = ctx.params
-  const { orgId } = ctx.state
+  const { orgId, userId } = ctx.state
+  console.log(ctx.state)
+
+  const hasProjectAccess = await verifyProjectAccess(projectId, userId)
+  const [user] = await sql`select * from account where id = ${userId}`
+
+  if (!hasProjectAccess) {
+    ctx.throw(401, "Not allowed")
+  }
+
+  if (user.role !== "admin") {
+    ctx.throw(403, "You must be an admin to delete a project")
+  }
 
   const [{ count }] =
     await sql`select count(*)::int from  project where org_id = ${orgId}`
 
   if (count > 1) {
     await sql`delete from project where id = ${projectId}`
     ctx.status = 200
+    ctx.body = {}
   } else {
     ctx.status = 422
 
     ctx.body = {
       error: "Deletion Failed",
diff --git a/packages/backend/src/utils/authorization.ts b/packages/backend/src/utils/authorization.ts
new file mode 100644
index 0000000..3cf0c4b
--- /dev/null
+++ b/packages/backend/src/utils/authorization.ts
@@ -0,0 +1,13 @@
+import sql from "./db"
+
+export async function verifyProjectAccess(projectId: string, userId: string) {
+  const [{ exists: hasAccess }] = await sql`
+      select exists (
+        select 1 
+        from project 
+        where org_id = (select org_id from account where id = ${userId}) 
+          and id = ${projectId}
+      )
+    `
+  return hasAccess
+}

Vulnerable code:   const { orgId } = ctx.state
Fixed code: import { verifyProjectAccess } from "@/src/utils/authorization"
  const { orgId, userId } = ctx.state
  console.log(ctx.state)

  const hasProjectAccess = await verifyProjectAccess(projectId, userId)
  const [user] = await sql`select * from account where id = ${userId}`

  if (!hasProjectAccess) {
    ctx.throw(401, "Not allowed")
  }

  if (user.role !== "admin") {
    ctx.throw(403, "You must be an admin to delete a project")
  }
    ctx.body = {}
import sql from "./db"

export async function verifyProjectAccess(projectId: string, userId: string) {
  const [{ exists: hasAccess }] = await sql`
      select exists (
        select 1 
        from project 
        where org_id = (select org_id from account where id = ${userId}) 
          and id = ${projectId}
      )
    `
  return hasAccess
}