CVE-2024-1666: Allocation of Resources Without Limits or Throttling (CWE-770)
value: In lunary-ai/lunary version 1.0.0, an authorization flaw exists that allows unauthorized radar creation. The vulnerability stems from the lack of server-side checks to verify if a user is on a free account during the radar creation process, which is only enforced in the web UI. As a result, attackers can bypass the intended account upgrade requirement by directly sending crafted requests to the server, enabling the creation of an unlimited number of radars without payment.
CWE description: The product allocates a reusable resource or group of resources on behalf of an actor without imposing any restrictions on the size or number of resources that can be allocated, in violation of the intended security policy for that actor.
Published: 2024-04-16T00:15Z
Repository: https://github.com/lunary-ai/lunary
Language: TypeScript
Files changed: packages/backend/src/api/v1/evaluations/index.ts, packages/backend/src/api/v1/projects/index.ts, packages/backend/src/api/v1/radars.ts
Commit date: 2024-04-08 23:35:58 -0300
Commit message: fix: security patches (#192)


Diff: diff --git a/packages/backend/src/api/v1/evaluations/index.ts b/packages/backend/src/api/v1/evaluations/index.ts
index 9b0a5ff4..bf9fc112 100644
--- a/packages/backend/src/api/v1/evaluations/index.ts
+++ b/packages/backend/src/api/v1/evaluations/index.ts
@@ -1,28 +1,27 @@
-import Router from "koa-router"
+import { runChecksOnRun } from "@/src/checks/runChecks"
+import { checkAccess } from "@/src/utils/authorization"
+import { calcRunCost } from "@/src/utils/calcCost"
+import { getReadableDateTime } from "@/src/utils/date"
 import sql from "@/src/utils/db"
 import Context from "@/src/utils/koa"
-import { getReadableDateTime } from "@/src/utils/date"
-import { runEval } from "./utils"
-import { getEvaluation } from "./utils"
-import { calcRunCost } from "@/src/utils/calcCost"
-import { runChecksOnRun } from "@/src/checks/runChecks"
+import Router from "koa-router"
+import { RunEvent } from "lunary/types"
 import PQueue from "p-queue"
 import { PassThrough } from "stream"
-import { checkAccess } from "@/src/utils/authorization"
-import { RunEvent } from "lunary/types"
+import { runEval } from "./utils"
 
 const evaluations = new Router({ prefix: "/evaluations" })
 
 const MAX_PARALLEL_EVALS = 4
 
 evaluations.post(
   "/",
   checkAccess("evaluations", "create"),
   async (ctx: Context) => {
     const { name, datasetId, checklistId, providers } = ctx.request.body as any
-    const { userId, projectId } = ctx.state
+    const { userId, projectId, orgId } = ctx.state
 
     ctx.request.socket.setTimeout(0)
     ctx.request.socket.setNoDelay(true)
     ctx.request.socket.setKeepAlive(true)
 
@@ -35,10 +34,16 @@ evaluations.post(
     const queue = new PQueue({
       concurrency: MAX_PARALLEL_EVALS,
       timeout: 10000,
     })
 
+    const [{ plan }] =
+      await sql`select plan, eval_allowance from org where id = ${orgId}`
+    if (plan === "free") {
+      ctx.throw(403, "You can't create evaluations on the free plan.")
+    }
+
     // TODO: transactions, but not working with because of nesting
     const evaluationToInsert = {
       name: name ? name : `Evaluation of ${getReadableDateTime()}`,
       ownerId: userId,
       projectId,
diff --git a/packages/backend/src/api/v1/projects/index.ts b/packages/backend/src/api/v1/projects/index.ts
index 3745798b..99c8f88c 100644
--- a/packages/backend/src/api/v1/projects/index.ts
+++ b/packages/backend/src/api/v1/projects/index.ts
@@ -119,11 +119,16 @@ projects.post(
   checkAccess("projects", "update"),
   async (ctx: Context) => {
     const { projectId } = ctx.params
     const { userId } = ctx.state
 
-    // Define the schema for request body validation using Zod
+    const [hasAccess] =
+      await sql`select * from account_project where project_id = ${projectId} and account_id = ${userId}`
+    if (!hasAccess) {
+      ctx.throw(401, "Not allowed")
+    }
+
     const requestBodySchema = z.object({
       type: z.enum(["private", "public"]),
     })
     const { type } = requestBodySchema.parse(ctx.request.body)
 
diff --git a/packages/backend/src/api/v1/radars.ts b/packages/backend/src/api/v1/radars.ts
index 68eca74a..8ff2babf 100644
--- a/packages/backend/src/api/v1/radars.ts
+++ b/packages/backend/src/api/v1/radars.ts
@@ -216,19 +216,25 @@ radars.get("/:radarId/chart", async (ctx) => {
     failed: Number(row.failed),
   }))
 })
 
 radars.post("/", checkAccess("radars", "create"), async (ctx) => {
-  const { projectId, userId } = ctx.state
+  const { projectId, userId, orgId } = ctx.state
   const { description, view, checks, alerts, negative } = ctx.request.body as {
     description: string
     view: any[]
     checks: any[]
     alerts: any[]
     negative: boolean
   }
 
+  const [{ plan }] =
+    await sql`select plan, eval_allowance from org where id = ${orgId}`
+  if (plan === "free") {
+    ctx.throw(403, "You can't create evaluations on the free plan.")
+  }
+
   const [row] = await sql`
     insert into radar ${sql({
       description,
       view: sql.json(view),
       checks: sql.json(checks),

Vulnerable code: import Router from "koa-router"
import { getReadableDateTime } from "@/src/utils/date"
import { runEval } from "./utils"
import { getEvaluation } from "./utils"
import { calcRunCost } from "@/src/utils/calcCost"
import { runChecksOnRun } from "@/src/checks/runChecks"
import { checkAccess } from "@/src/utils/authorization"
import { RunEvent } from "lunary/types"
    const { userId, projectId } = ctx.state
    // Define the schema for request body validation using Zod
  const { projectId, userId } = ctx.state
Fixed code: import { runChecksOnRun } from "@/src/checks/runChecks"
import { checkAccess } from "@/src/utils/authorization"
import { calcRunCost } from "@/src/utils/calcCost"
import { getReadableDateTime } from "@/src/utils/date"
import Router from "koa-router"
import { RunEvent } from "lunary/types"
import { runEval } from "./utils"
    const { userId, projectId, orgId } = ctx.state
    const [{ plan }] =
      await sql`select plan, eval_allowance from org where id = ${orgId}`
    if (plan === "free") {
      ctx.throw(403, "You can't create evaluations on the free plan.")
    }

    const [hasAccess] =
      await sql`select * from account_project where project_id = ${projectId} and account_id = ${userId}`
    if (!hasAccess) {
      ctx.throw(401, "Not allowed")
    }

  const { projectId, userId, orgId } = ctx.state
  const [{ plan }] =
    await sql`select plan, eval_allowance from org where id = ${orgId}`
  if (plan === "free") {
    ctx.throw(403, "You can't create evaluations on the free plan.")
  }