
CVE-2026-49268 — Análisis y remediación de una vulnerabilidad de omisión de autenticación por inyección LDAP
| Rama | 1.13-CVE-2026-49268 |
| Autor | Jinwoo Hwang (https://JinwooHwang.com) |
Esta rama (1.13-CVE-2026-49268) contiene una remediación de seguridad integral de una Vulnerabilidad de Omisión de Autenticación por Inyección LDAP en la versión 1.13 de Apache Shiro.
Descripción oficial del NVD
Un atacante remoto puede inyectar caracteres especiales LDAP en la construcción del Distinguished Name (DN) en la clase DefaultLdapRealm. La entrada de nombre de usuario proporcionada por el usuario se concatena directamente en la plantilla de DN LDAP sin ningún escape de los caracteres especiales de RFC 2253. Esto permite a un atacante manipular la estructura del DN utilizada para la autenticación bind LDAP, potencialmente omitiendo la autenticación o suplantando a otros usuarios. Este problema afecta a todas las versiones de Apache Shiro hasta la 2.2.0, y a la 3.0.0-alpha-1 cuando se utiliza DefaultLdapRealm.
| Campo | Valor |
|---|---|
| CVE | CVE-2026-49268 — Apache Shiro: Inyección de DN LDAP en DefaultLdapRealm |
| ID de CWE | CWE-90 — Neutralización incorrecta de elementos especiales utilizados en una consulta LDAP ('Inyección LDAP') |
| CVSS v4.0 | 8.8 ALTO — 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 CRÍTICO — CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N |
| Versiones afectadas | org.apache.shiro:shiro-core 0 hasta 2.2.0 inclusive; 3.0.0-alpha-0 hasta 3.0.0-alpha-1 inclusive |
| Corregido en upstream en | 2.2.1 y 3.0.0-alpha-2 |
| Referencia | https://lists.apache.org/thread/svszql3od8td7hn6conyj2oq70v53b5s |
Apache Shiro puede autenticar usuarios contra un directorio LDAP. Para ello, convierte un
nombre de usuario enviado en un Distinguished Name — la dirección del directorio para la entrada de ese usuario —
insertando el nombre de usuario en una plantilla configurada, por ejemplo
uid={0},ou=users,dc=mycompany,dc=com.
En las versiones afectadas, el nombre de usuario se pega en esa plantilla como texto sin formato. Un puñado de caracteres de puntuación — el más importante la coma — no son texto ordinario para un servidor de directorio; son la sintaxis que separa una parte de una dirección de la siguiente. Un nombre de usuario que contiene esos caracteres, por lo tanto, deja de comportarse como un valor dentro de la dirección y comienza a comportarse como parte de la dirección misma.
La consecuencia práctica es que una persona que inicia sesión puede influir en contra qué entrada del directorio Shiro intenta autenticarse, en lugar de solo proporcionar su propio nombre. En lugar de buscarse en el contenedor que la implementación pretendía, la búsqueda puede redirigirse a otra parte del directorio. Dependiendo de cómo esté organizado el directorio y qué permita, esto puede llevar a autenticarse como la identidad equivocada, o a que la autenticación tenga éxito cuando no debería.
Perfil de riesgo. Lo que aumenta el riesgo: no se requieren accesos ni credenciales previos — la
entrada llega al límite de inicio de sesión, que es accesible por cualquiera que pueda alcanzar la
aplicación; y la clase afectada es la forma estándar y documentada de conectar Shiro a LDAP,
por lo que no es una configuración exótica. Lo que lo reduce: la implementación debe usar realmente
DefaultLdapRealm (o JndiLdapRealm) con una plantilla de DN configurada; las implementaciones que
se autentican por otros medios, o que pasan un DN completo o una credencial no textual como un
certificado, no se ven afectadas. Si una búsqueda redirigida produce una autenticación utilizable
también depende de la propia organización y las reglas de acceso del directorio de destino, que varían según el sitio.
Una segunda consecuencia, no relacionada con la seguridad, merece mención para la planificación de versiones: los usuarios cuyos
nombres de usuario legítimos contienen una barra invertida o comienzan con # actualmente no pueden iniciar sesión en
absoluto, porque la dirección construida para ellos no es un nombre bien formado y es rechazada
de plano. Otra puntuación no falla de plano — cambia silenciosamente a qué entrada
se refiere la dirección, que es el problema de seguridad anterior. La misma corrección resuelve ambos.
core/src/main/java/org/apache/shiro/realm/ldap/DefaultLdapRealm.java, getUserDn(String),
líneas 227–250 en la línea base sin corregir:```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();
}
La plantilla se divide una vez, en tiempo de configuración, alrededor del token `{0}`
(`setUserDnTemplate`, líneas 181–200), en un `prefix` y un `suffix`. En el momento de la autenticación
el principal se concatena entre ellos. **No se aplica ninguna codificación en ningún punto.** No hay
ningún helper de escape en ninguna parte de `org/apache/shiro/realm/ldap/`.
### 3.2 La invariante que se asumía pero nunca se aplicaba
El código circundante trata el resultado de `getUserDn` como un Distinguished Name bien formado —
se pasa directamente a `LdapContextFactory.getLdapContext(...)` y de ahí a JNDI como un
bind DN. Eso solo es correcto si el principal sustituido es un *único valor de atributo*.
La concatenación de cadenas no puede garantizar eso. RFC 2253 (y RFC 4514) reservan
`,` `+` `"` `\` `<` `>` `;` `=`, un `#` inicial, y espacios en blanco iniciales/finales como sintaxis
estructural dentro de un DN. Cuando cualquiera de esos aparece en el principal, son leídos por el analizador como
estructura, no como contenido. La invariante — *"el principal ocupa exactamente un valor de RDN"* —
era asumida por todos los consumidores posteriores y no era aplicada por ninguno.