CVE-2026-90979 LDAPCache 和 LDAPBackingEngine LDAP 注入漏洞
影响可导致LDAP查询结果被篡改,越权匹配用户或角色
LDAPCache 与 LDAPBackingEngine 在构造 LDAP 搜索过滤器时,将登录名、用户 DN 等值直接文本替换进管理员配置的 userFilter、roleFilter 模板。替换前仅对反斜杠做了转义,未按 RFC 4515 转义 *、(、) 和 NUL 等字符,攻击者可通过构造含特殊字符的登录名改变过滤器结构。
影响范围
受影响组件为 LDAPCache 与 LDAPBackingEngine;具体产品与版本范围暂无公开信息。
漏洞详情
漏洞类型为 LDAP 过滤器注入(CWE-90)。成因是使用字符串替换把 %u、%dn、%fqdn 占位符替换为登录名等值,且只转义了反斜杠,遗漏了 RFC 4515 要求转义的 *、(、) 和 NUL。利用方式是提交含这些字符的登录名,使等值匹配变为通配匹配或闭合/重开过滤子句,从而扩大搜索结果。
利用条件与风险
并非所有入口点都可利用,需目标使用可注入的过滤器模板且登录名可控;成功利用可越权匹配其他 LDAP 条目、过度授予角色,甚至改变登录解析到的账户。
修复建议
官方修复方案为按 RFC 4515 对替换值中的 *、(、) 和 NUL 等特殊字符进行完整转义;临时缓解措施暂无公开信息。
LDAPCache and LDAPBackingEngine build LDAP search filters for user lookup and role lookup by textually substituting the placeholders %u, %dn, and %fqdn (drawn from the login name, the resolved user DN, and its fully qualified namespace form) into administrator-configured filter templates (userFilter, roleFilter). Before the fix, the only sanitization applied to the substituted value was double backslashed:
filter = filter.replaceAll(Pattern.quote(“%u”), Matcher.quoteReplacement(user));
filter = filter.replace(“\”, “\\”);
This does not escape the other characters RFC 4515 requires escaping in an LDAP search filter: *, (, ), and NUL. A login name containing any of these can change the structure of the resulting filter rather than being matched as a literal value (e.g. a crafted username can turn an equality match into a wildcard match, or close/reopen filter clauses), widening what the search returns and potentially causing a login or role lookup to match an LDAP entry other than the intended one, over-granting roles, and depending on deployment-specific filter templates, potentially affecting which account a login resolved to.
It’s not exploitable through every entry points: LDAPLoginModule and LDAPPubkeyLoginModule both called Util.doRFC2254Encoding() (correct RFC 4515 escaping) on the login name before handing it to LDAPCache, which masked the missing escaping in LDAPCache for those two call paths. Using LDAPCache directly (bypassing the login modules) does not reproduce through the normal LDAPLoginModule/LDAPPubkeyLoginModule authentication flow for this reason. It does reproduce through two other call paths that reach LDAPCache/LDAPBackingEngine without any prior escaping:
* GSSAPILdapLoginModule passes the NameCallback name straight through, unescaped.
* LDAPBackingEngine (listRoles) passes principal.getName() straight through, unescaped.