
CVE-2026-49268 — تحليل ومعالجة ثغرة تجاوز المصادقة عبر حقن LDAP
| الفرع | 1.13-CVE-2026-49268 |
| المؤلف | Jinwoo Hwang (https://JinwooHwang.com) |
يحتوي هذا الفرع (1.13-CVE-2026-49268) على معالجة أمنية شاملة لثغرة تجاوز المصادقة عبر حقن LDAP في إصدار Apache Shiro 1.13.
وصف NVD الرسمي
يمكن لمهاجم عن بُعد حقن أحرف LDAP الخاصة في بناء Distinguished Name (DN) في فئة DefaultLdapRealm. يتم ربط إدخال اسم المستخدم الذي يوفره المستخدم مباشرةً في قالب LDAP DN دون أي تهريب لأحرف RFC 2253 الخاصة. يتيح ذلك للمهاجم التلاعب ببنية DN المستخدمة في مصادقة ربط LDAP، مما قد يؤدي إلى تجاوز المصادقة أو انتحال هوية مستخدمين آخرين. تؤثر هذه المشكلة على جميع إصدارات Apache Shiro حتى 2.2.0، و3.0.0-alpha-1 عند استخدام DefaultLdapRealm.
| الحقل | القيمة |
|---|---|
| CVE | CVE-2026-49268 — Apache Shiro: حقن LDAP DN في DefaultLdapRealm |
| CWE ID | CWE-90 — التحييد غير السليم للعناصر الخاصة المستخدمة في استعلام LDAP ('حقن LDAP') |
| CVSS v4.0 | 8.8 HIGH — CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:H/VA:N/SC:N/SI:N/SA:N/S:P/AU:Y/R:A/RE:L/U:Red |
| CVSS v3.1 | 9.1 CRITICAL — CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N |
| الإصدارات المتأثرة | org.apache.shiro:shiro-core 0 حتى 2.2.0 ضمناً؛ 3.0.0-alpha-0 حتى 3.0.0-alpha-1 ضمناً |
| تم الإصلاح في الإصدارات الأصلية | 2.2.1 و3.0.0-alpha-2 |
| المرجع | https://lists.apache.org/thread/svszql3od8td7hn6conyj2oq70v53b5s |
يمكن لـ Apache Shiro مصادقة المستخدمين مقابل دليل LDAP. وللقيام بذلك، يحوّل
اسم المستخدم المُرسَل إلى Distinguished Name — عنوان الدليل الخاص بمدخل ذلك المستخدم —
عن طريق إدراج اسم المستخدم في قالب مُهيَّأ، على سبيل المثال
uid={0},ou=users,dc=mycompany,dc=com.
في الإصدارات المتأثرة يتم لصق اسم المستخدم في ذلك القالب كنص خام. وهناك مجموعة من أحرف الترقيم — وأهمها الفاصلة — ليست نصاً عادياً بالنسبة لخادم الدليل؛ بل هي الصيغة التي تفصل جزءاً من العنوان عن الجزء التالي. لذلك يتوقف اسم المستخدم الذي يحتوي على تلك الأحرف عن التصرف كقيمة داخل العنوان ويبدأ في التصرف كجزء من العنوان نفسه.
النتيجة العملية هي أن الشخص الذي يسجّل الدخول يمكنه التأثير على أي مدخل دليل يحاول Shiro المصادقة مقابله، بدلاً من مجرد تقديم اسمه الخاص. فبدلاً من البحث عنه في الحاوية التي قصدها النشر، يمكن إعادة توجيه البحث إلى مكان آخر في الدليل. واعتماداً على كيفية تنظيم الدليل وما يسمح به، قد يؤدي ذلك إلى المصادقة بهوية خاطئة، أو إلى نجاح المصادقة عندما لا ينبغي لها ذلك.
ملف المخاطر. ما يرفع المخاطر: لا يُشترط أي وصول مسبق أو بيانات اعتماد — يصل
الإدخال عند حدود تسجيل الدخول، التي يمكن لأي شخص يستطيع الوصول إلى التطبيق
الوصول إليها؛ والفئة المتأثرة هي الطريقة القياسية والموثّقة لربط Shiro بـ LDAP،
لذا فهذا ليس إعداداً غريباً. وما يخفضها: يجب أن يستخدم النشر فعلياً
DefaultLdapRealm (أو JndiLdapRealm) مع قالب DN مُهيَّأ؛ أما عمليات النشر
التي تصادق بوسائل أخرى، أو التي تمرر DN كاملاً أو بيانات اعتماد غير نصية مثل
شهادة، فهي غير متأثرة. كما أن ما إذا كان البحث المُعاد توجيهه يُنتج مصادقة قابلة
للاستخدام يعتمد أيضاً على تنظيم الدليل الهدف وقواعد الوصول الخاصة به، والتي تختلف
من موقع لآخر.
تجدر الإشارة إلى نتيجة ثانية غير أمنية لأغراض تخطيط الإصدارات: المستخدمون الذين
تحتوي أسماء مستخدميهم الشرعية على شرطة مائلة عكسية أو تبدأ بـ # لا يمكنهم
حالياً تسجيل الدخول إطلاقاً، لأن العنوان المبني لهم ليس اسماً سليماً ويُرفض
تماماً. أما علامات الترقيم الأخرى فلا تفشل تماماً — بل تغيّر بصمت المدخل الذي
يشير إليه العنوان، وهي المشكلة الأمنية المذكورة أعلاه. ويحل الإصلاح نفسه كلا
المشكلتين.
core/src/main/java/org/apache/shiro/realm/ldap/DefaultLdapRealm.java، getUserDn(String)،
الأسطر 227–250 على الأساس غير المُصلَّح:```java
protected String getUserDn(String principal) throws IllegalArgumentException, IllegalStateException {
if (!StringUtils.hasText(principal)) {
throw new IllegalArgumentException("User principal cannot be null or empty for User DN construction.");
}
String prefix = getUserDnPrefix();
String suffix = getUserDnSuffix();
if (prefix == null && suffix == null) {
log.debug("userDnTemplate property has not been configured, indicating the submitted "
+ "AuthenticationToken's principal is the same as the User DN. Returning the method argument "
+ "as is.");
return principal;
}
int prefixLength = prefix != null ? prefix.length() : 0;
int suffixLength = suffix != null ? suffix.length() : 0;
StringBuilder sb = new StringBuilder(prefixLength + principal.length() + suffixLength);
if (prefixLength > 0) {
sb.append(prefix);
}
sb.append(principal); // <-- inserted verbatim
if (suffixLength > 0) {
sb.append(suffix);
}
return sb.toString();
}
يُقسَّم القالب مرة واحدة، عند وقت التهيئة، حول الرمز `{0}`
(`setUserDnTemplate`، الأسطر 181–200)، إلى `prefix` و`suffix`. عند وقت المصادقة
يُدمَج الـ principal بينهما. **لا يُطبَّق أي ترميز في أي نقطة.** لا يوجد
أي مساعد تهريب في أي مكان ضمن `org/apache/shiro/realm/ldap/`.
### 3.2 الثابت الذي افتُرض لكن لم يُفرَض قط
تتعامل الشيفرة المحيطة مع ناتج `getUserDn` باعتباره Distinguished Name جيد التكوين —
إذ يُمرَّر مباشرة إلى `LdapContextFactory.getLdapContext(...)` ومن هناك إلى JNDI كـ
bind DN. وهذا لا يكون سليماً إلا إذا كان الـ principal المُستبدَل *قيمة سمة واحدة*.
لا يمكن لدمج النصوص فرض ذلك. تحجز RFC 2253 (و RFC 4514) الرموز
`,` `+` `"` `\` `<` `>` `;` `=`، و`#` البادئة، والمسافات البيضاء البادئة/اللاحقة كبنية
نحوية داخل الـ DN. وعندما يظهر أي منها في الـ principal فإنها تُقرأ من قِبَل المحلِّل
كبنية، لا كمحتوى. الثابت — *"يشغل الـ principal قيمة RDN واحدة بالضبط"* —
افتُرض من قِبَل كل مستهلك لاحق ولم يفرضه أيٌّ منهم.
### 3.3 تدفق التنفيذ، من نقطة الدخول إلى العيب```
submitted credentials (username, password)
│
▼
DefaultLdapRealm.doGetAuthenticationInfo(AuthenticationToken) [line 292]
│
▼
DefaultLdapRealm.getLdapPrincipal(AuthenticationToken) [line 338]
│ principal instanceof String ?
│ ├── no ──► return principal unchanged ── NOT AFFECTED (e.g. X.509)
│ └── yes ──┐
▼ │
DefaultLdapRealm.getUserDn(String) [line 227]
│ prefix == null && suffix == null ?
│ ├── yes ──► return principal unchanged ── NOT AFFECTED ("principal IS the DN")
│ └── no ──┐
▼ │
prefix + principal + suffix ◄── DEFECT: unencoded concatenation [line 246]
│
▼
LdapContextFactory.getLdapContext(userDn, credentials)
│
▼
JNDI bind against the directory using the constructed DN
القالب uid={0},ou=users,dc=mycompany,dc=com، والهوية الأساسية jsmith,ou=admins:
| الـ DN المُنشأ | الـ RDNs المُحلَّلة | |
|---|---|---|
| المقصود | uid=jsmith\,ou\=admins,ou=users,dc=mycompany,dc=com | 4 |
| الأساس (غير المُصلَّح) | uid=jsmith,ou=admins,ou=users,dc=mycompany,dc=com | 5 |
يكتسب الاسم مكوّنًا إضافيًا. لم يعد ou=users هو الحاوية المُستهدَفة —
بل تم إقحام ou=admins في المنتصف. تم التحقق من ذلك عبر التحليل باستخدام javax.naming.ldap.LdapName، وهو
مستقل عن أي تطبيق للتهريب.
السلوك المقيس للأساس عبر المجموعة المحجوزة (JDK 8، تحليل LdapName):