
PoC — إساءة استخدام الوكيل عبر الأصول المتقاطعة لمفاتيح API الخاصة بالمزوّد المُهيّأة في PasteGuard (GHSA-q94x-p9rc-q89f، CVE-2026-86998، CVSS 7.6).
حالة CVE: مطلوب، في انتظار التعيين. تم نشر هذا الاكتشاف باسم GHSA-q94x-p9rc-q89f. عند تعيين CVE، يُعاد تسمية هذا المستودع إلى
CVE-YYYY-NNNNN-pasteguard-PoCوتُستبدل هذه اللافتة برابط CVE.
| الباحث | Dostxodjayev Abdullox (@squeeze440) |
| التنبيه | GHSA-q94x-p9rc-q89f |
| CVSS 3.1 | 7.6 (مرتفع) |
| الضعف | CWE-352، CWE-942 |
الملخص: غياب حماية CORS/CSRF على مسارات وكيل LLM في PasteGuard 0.9.1 يسمح لمهاجم عن بُعد (أي موقع ويب يزوره متصفح المشغّل، أو أي مضيف على الشبكة المحلية) بإطلاق طلبات موثّقة إلى واجهة OpenAI/Anthropic API المُهيّأة لدى المشغّل باستخدام مفتاح API الاحتياطي من جانب الخادم الخاص بـ PasteGuard، وقراءة الاستجابة، عبر fetch() عابر للأصول إلى /openai/v1/chat/completions (والمسار الشقيق /anthropic/v1/messages).
المنتج: PasteGuard (github.com/sgasser/pasteguard)
الإصدار المُختبَر: الالتزام 100718191499c52934f3b2ccca72ca16373fb765 (2026-07-30)، إصدار package.json 0.9.1
CVSS v3.1 المُقدَّر: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:H — 7.6 (مرتفع)
UI:R — يجب أن يفتح متصفح المشغّل صفحة (أي صفحة، على أي موقع) أثناء تشغيل PasteGuard؛ لا حاجة لأي تفاعل آخر.C:L / I:L — جافاسكريبت المهاجم يقرأ فقط استجابة الطلب الذي اختاره المهاجم نفسه (وليس محادثات المشغّل السابقة/بيانات لوحة التحكم، وهي مستبعدة بشكل صحيح من CORS)، لكن هذا لا يزال يؤكد/يستخدم حسابًا ممولًا نشطًا ويلوّث سجل الاستخدام/التدقيق للحساب بهوية المشغّل.A:H — التأثير الواقعي غير المحدود: يمكن لصفحة المهاجم تكرار هذا الطلب بلا نهاية، مما يدفع استخدامًا مُفوترًا حقيقيًا ضد حساب/حصة مزوّد المشغّل (استنزاف مالي، احتمال استنفاد حدود المعدل أو رفع علامات إساءة الاستخدام من جانب المزوّد) دون وجود أي تحديد للمعدل أو فحص للأصل في الطريق.S:U — يبقى التأثير ضمن نفس حدود الثقة (بيانات الاعتماد المُهيّأة للوكيل نفسه هي ما يُستخدم)؛ لا يُعبَر أي سلطة أمنية منفصلة.التفاصيل:
تُطبّق طبقة HTTP في PasteGuard سياسة CORS متساهلة وشاملة (wildcard) على كل مسار باستثناء لوحة التحكم:
src/index.ts:40-45 — const corsMiddleware = cors(); (دالة cors() في Hono بدون خيارات تُعيّن افتراضيًا Access-Control-Allow-Origin: *) تُطبَّق عبر app.use("*", ...) على كل شيء، مع استثناء صريح فقط لـ /dashboard و /dashboard/*. يُظهر التعليق الموجود مباشرة أعلاه (src/index.ts:34-39) أن المؤلف فكّر بعناية في تعرّض لوحة التحكم لـ CORS لكنه لم يمدّد نفس التفكير إلى مسارات الوكيل أسفلها.src/config.ts:153 — host: z.string().default("0.0.0.0")، مؤكَّد فعليًا في config.example.yaml:13. يستمع الوكيل على جميع الواجهات افتراضيًا، وليس فقط loopback، لذا يمكن الوصول إليه من الشبكة المحلية أيضًا، وليس فقط من متصفح المشغّل نفسه.src/providers/openai/client.ts:48-53:
// Use client's auth header if provided, otherwise fall back to config
if (authHeader) {
headers.Authorization = authHeader;
} else if (config.api_key) {
headers.Authorization = `Bearer ${config.api_key}`;
}
Authorization، يُرفق PasteGuard بصمت مفتاحه الخاص المحتفظ به على الخادم providers.openai.api_key بالطلب الصادر. يوثّق config.example.yaml:22-24 هذا كميزة راحة مقصودة ("احتياطي اختياري إذا لم يرسل العميل ترويسة المصادقة") لحالة الاستخدام "Apps & APIs".src/providers/anthropic/client.ts:37-61 يُطبّق نفس نمط الاحتياطي لـ /anthropic/v1/messages (احتياطي x-api-key/Authorization إلى config.api_key) — مؤكَّد كحالة شقيقة لنفس السبب الجذري، ولم يُعَد التحقق منه بشكل مستقل من البداية إلى النهاية (انظر نطاق PoC أدناه).لأن سياسة CORS الشاملة تقع أمام مسارات تحمل بيانات الاعتماد الاحتياطية هذه، يمكن لأي أصل أن (1) يجعل المتصفح يرسل الطلب بدون ترويسة Authorization، مما يدفع الخادم لإرفاق مفتاح API الحقيقي الخاص به، و(2) يقرأ استجابة JSON، لأن Access-Control-Allow-Origin: * موجود. لا يوجد فحص Origin/Referer أو رمز CSRF يحمي هذه المسارات.
إثبات المفهوم (مؤكَّد ديناميكيًا، وليس مجرد تتبع ساكن):
config.yaml مع providers.openai.base_url: http://127.0.0.1:9091 (منبع وهمي) و providers.openai.api_key: "sk-VICTIM-SECRET-DO-NOT-LEAK-12345"، و pii_detection.enabled: false (كاشف وهمي /health فقط، لتجنّب الحاجة إلى خدمة نموذج GLiNER الكاملة)، و secrets_detection.enabled: false. تم تشغيل PasteGuard بـ bun run src/index.ts، وتأكيد /health → 200.mock_upstream.py) على 127.0.0.1:9091 يسجّل أي ترويسة Authorization يستقبلها.http://127.0.0.2:8001/attack.html (عنوان IP loopback حرفي مختلف، وفقًا لقواعد عزل المواقع في Chrome — وليس اختبار localhost من نفس الأصل)، عبر python3 -m http.server 8001 --bind 127.0.0.2. الإجراء الوحيد للصفحة عند التحميل:
fetch("http://127.0.0.1:3000/openai/v1/chat/completions", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ model: "gpt-4o-mini", messages: [{ role: "user", content: "cross-origin drive-by call, attacker supplied NO api key" }] })
}).then(r => r.json()).then(data => { /* render on page */ });
http://127.0.0.2:8001/attack.html. تم التأكيد عبر فحص شبكة DevTools:
origin: http://127.0.0.2:8001، sec-fetch-site: cross-site، لا ترويسة Authorization أرسلتها صفحة المهاجم.access-control-allow-origin: *، HTTP 200، جسم JSON قابل للقراءة بالكامل بواسطة جافاسكريبت صفحة المهاجم.../evidence/cross_origin_csrf_success.png.RECEIVED AUTH HEADER: Bearer sk-VICTIM-SECRET-DO-NOT-LEAK-12345 — مما يثبت أن PasteGuard أرفق مفتاح المشغّل الحقيقي المُهيّأ من جانب الخادم، دون أن تعرف صفحة المهاجم به أو توفّره قط.الأدلة الخام: ~/engagements/pasteguard/evidence/cross_origin_csrf_success.png، ~/engagements/pasteguard/evidence/mock_upstream_log.txt.
تم تأكيد مسار /anthropic/v1/messages بشكل ساكن (ملف:سطر أعلاه) على أنه يشارك نفس نمط الاحتياطي + CORS الشامل لكنه لم يُعَد تشغيله بشكل منفصل عبر PoC المتصفح الحي، مراعاةً لتوجيه الجلسة الخاص بالعمق على الاتساع/قاعدة الخمس دقائق بمجرد تأكيد السبب الجذري بالفعل على /openai.
التأثير: أي موقع ويب يزوره متصفح مشغّل PasteGuard (إعلان خبيث، موقع مخترق، أو مهاجم على نفس الشبكة المحلية نظرًا للربط الافتراضي 0.0.0.0) يمكنه بصمت دفع طلبات مُفوترة غير محدودة عبر حساب OpenAI/Anthropic الخاص بالمشغّل نفسه من خلال نسخة PasteGuard المحلية لديه، دون أي تفاعل من المستخدم يتجاوز إبقاء التبويب مفتوحًا ودون أي طريقة للمشغّل لملاحظة ذلك سوى مراقبة لوحة فواتير المزوّد. هذا ناقل مباشر لإساءة الاستخدام المالي / استنفاد الحصة، وثانويًا يلوّث سجل استخدام/تدقيق حساب المزوّد لدى المشغّل بمحتوى اختاره المهاجم.
نقاط الضعف:
المعالجة (اقتراحات):
cors() الشامل على /openai، /anthropic، /codex (و /api/mask إذا امتدّ في أي وقت لاستخدام بيانات اعتماد محتفظ بها على الخادم) عند تهيئة api_key احتياطي. كحد أدنى، اجعل الأصول المسموح بها لهذه المسارات صريحة/قابلة للتهيئة (مثلًا فقط أصل إضافة المتصفح المعروف)، مع الافتراض بعدم الوصول العابر للأصول بدلًا من *./dashboard في src/index.ts.server.host من 0.0.0.0 إلى 127.0.0.1 في src/config.ts:153 و config.example.yaml:13، مع اشتراط موافقة صريحة للربط على جميع الواجهات.الفضل: Dostxodjayev Abdullox
قناة الإبلاغ: لا يوجد SECURITY.md في المستودع (مؤكَّد عبر gh api repos/sgasser/pasteguard/contents/SECURITY.md → 404، أُعيد التحقق منه في وقت التدقيق). لا توجد تنبيهات أمنية منشورة سابقًا أيضًا (gh api repos/sgasser/pasteguard/security-advisories → [])، لذا لا يبدو أن هذا تكرار لمشكلة مُفصَح عنها سابقًا. القناة الموصى بها: تدفق الإبلاغ الخاص الافتراضي عن الثغرات في GitHub على https://github.com/sgasser/pasteguard/security/advisories/new.