Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
shiro — CVE-2026-49268 — Análisis y remediación de una vulnerabilidad de omisión de autenticación por inyección LDAP | Kitploit
Herramientas/GitHubGitHub/sassoftware/shiro
Análisis Estático de Código (SAST)Análisis de VulnerabilidadesAnálisis de CódigoSeguridad WebAutenticaciónAprendizaje y Educación
GitHubsassoftware/shiro

shiro

CVE-2026-49268 — Análisis y remediación de una vulnerabilidad de omisión de autenticación por inyección LDAP

Ver Repositorio
1hace 6 díasAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

CVE-2026-49268 — Análisis y Remediación de una Vulnerabilidad de Omisión de Autenticación por Inyección LDAP

Rama1.13-CVE-2026-49268
AutorJinwoo 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.


1. Descripción general de la vulnerabilidad


2. Resumen ejecutivo

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.


3. Análisis de la causa raíz

3.1 El defecto

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; }

root@kitploit:~
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();

}

root@kitploit:~
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.

### 3.3 Flujo de ejecución, desde el punto de entrada hasta el defecto```
  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

3.4 Efecto demostrado

Plantilla uid={0},ou=users,dc=mycompany,dc=com, principal jsmith,ou=admins:

DN construidoRDN analizados
Previstouid=jsmith\,ou\=admins,ou=users,dc=mycompany,dc=com4
Línea base (sin corregir)uid=jsmith,ou=admins,ou=users,dc=mycompany,dc=com5

El nombre gana un componente. ou=users ya no es el contenedor al que se dirige — se interpone ou=admins. Verificado mediante el análisis con javax.naming.ldap.LdapName, que es independiente de cualquier implementación de escape.

Comportamiento medido de la línea base sobre el conjunto reservado (JDK 8, análisis con LdapName):

Dos caracteres alteran la estructura del nombre, no solo su contenido: la coma y el punto y coma — que RFC 1779 acepta como separador de RDN alternativo. + introduce un segundo valor de atributo en el mismo RDN. Solo \ y un # inicial hacen que el nombre no se pueda analizar.

3.5 Código adyacente que es correcto, y por qué — radio de impacto

  • userDnTemplate sin establecer. getUserDn devuelve el principal sin modificar. Este es el modo documentado "el principal enviado es el DN"; el llamador proporciona un DN completo y es responsable de su corrección. No afectado.
  • Principales que no son String. getLdapPrincipal solo enruta los principales String hacia getUserDn; cualquier otra cosa (certificados X.509, tokens personalizados) pasa de largo. No afectado.
  • Validación de setUserDnTemplate (líneas 181–200) rechaza correctamente una plantilla nula, en blanco o sin {0}. Valida la plantilla proporcionada por el operador, que nunca fue la entrada no confiable — por lo que es código correcto que simplemente no aborda este defecto.
  • JndiLdapRealm extiende DefaultLdapRealm y no sobrescribe getUserDn, por lo que hereda el defecto. Confirmado mediante prueba (§6.1). En alcance, y corregido por el mismo cambio.

3.6 Ruta relacionada — EVALUADA, NO AFECTADA

AbstractLdapRealm.searchFilter (línea 89) contiene una segunda sustitución de {0}, por defecto (&(objectClass=*)(userPrincipalName={0})), utilizada en la ruta de autorización. No está afectada.

Un barrido de cada llamada a search(, cada llamador de getLdapContext( y cada literal de cadena con forma de LDAP en las fuentes principales de core, support y web encontró exactamente un uso de searchFilter — ActiveDirectoryRealm.getRoleNamesForUser, línea 172:```java Object[] searchArguments = new Object[]{userPrincipalName}; NamingEnumeration answer = ldapContext.search(searchBase, searchFilter, searchArguments, searchCtls);

root@kitploit:~
Este es el overload **parametrizado** `DirContext.search(String, String, Object[], SearchControls)`.
El nombre de usuario se pasa como *argumento* del filtro, nunca se concatena en la cadena
del filtro. Según el javadoc de `javax.naming.directory.DirContext` del JDK 8, textualmente:

> "Cuando un argumento de filtro con valor de cadena se sustituye por una variable, el filtro se
> interpreta como si la cadena se hubiera proporcionado en lugar de la variable, con cualquier carácter
> que tenga significado especial dentro de los filtros (como `'*'`) escapado de acuerdo
> con las reglas de la RFC 2254."

El proveedor JNDI realiza el escapado. El registro histórico de esto está en la propia
declaración del campo.

**Fuente: `core/src/main/java/org/apache/shiro/realm/ldap/AbstractLdapRealm.java`, líneas 88–89**```java
    //SHIRO-115 - prevent potential code injection:
    protected String searchFilter = "(&(objectClass=*)(userPrincipalName={0}))";

Fuente: core/src/main/java/org/apache/shiro/realm/activedirectory/ActiveDirectoryRealm.java, líneas 158–172```java protected Set getRoleNamesForUser(String username, LdapContext ldapContext) throws NamingException { Set roleNames; roleNames = new LinkedHashSet();

root@kitploit:~
    SearchControls searchCtls = new SearchControls();
    searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);

    String userPrincipalName = username;
    if (principalSuffix != null && !userPrincipalName.toLowerCase(Locale.ROOT).endsWith(principalSuffix.toLowerCase(Locale.ROOT))) {
        userPrincipalName += principalSuffix;
    }

    Object[] searchArguments = new Object[]{userPrincipalName};

    NamingEnumeration answer = ldapContext.search(searchBase, searchFilter, searchArguments, searchCtls);
root@kitploit:~
El nombre de usuario llega al directorio como `searchArguments[0]`, nunca como texto insertado en
`searchFilter`. El token `{0}` en el filtro es resuelto por el proveedor JNDI, no por
Shiro.

`ActiveDirectoryRealm` en la línea 108 pasa el nombre de usuario sin procesar a
`getLdapContext(username, password)`. Ese valor se convierte en una única entrada de entorno
`SECURITY_PRINCIPAL` de JNDI; no se sustituye en una plantilla ni se construye un DN a partir de él,
por lo que queda fuera del mecanismo de este defecto.

**Verificado empíricamente contra un servidor de directorio en vivo.** El escape de JNDI no se aceptó
sin más: se observó en el tráfico de red. Un servidor LDAP en proceso
(UnboundID `InMemoryDirectoryServer`) fue instrumentado con un
`InMemoryOperationInterceptor` que registra el filtro de cada solicitud de búsqueda que recibe,
y el `searchFilter` predeterminado de Shiro se emitió a través de la misma llamada JNDI parametrizada que
utiliza `ActiveDirectoryRealm`, con un argumento manipulado:```
filter template : (&(objectClass=*)(userPrincipalName={0}))
argument passed : *)(uid=jsmith
server received : (&(objectClass=*)(userPrincipalName=\2a\29\28uid=jsmith))

El *, ) y ( llegaron al servidor como \2a, \29 y \28 — escapados según RFC 2254. El filtro conserva exactamente dos cláusulas; el argumento no pudo añadir una tercera.

Control, la misma plantilla con el argumento insertado como texto sin procesar en lugar de pasarse como argumento de filtro:``` CONTROL, raw splice: (&(objectClass=)(userPrincipalName=)(uid=jsmith)) server received : (&(objectClass=)(userPrincipalName=)(uid=jsmith))

root@kitploit:~
El control llega al servidor con su estructura alterada — se añade una tercera cláusula y la prueba de `userPrincipalName` queda reducida a un comodín. La forma parametrizada no. Esto confirma, por observación más que por contrato, que la ruta `searchFilter` no se ve afectada.

---

## 4. Pasos de reproducción paso a paso

Deterministas, desde un checkout limpio. El directorio de trabajo es la raíz del repositorio en todo momento.

### 4.1 Requisitos previos

| Requisito | Valor utilizado |
|---|---|
| JDK | **8** — la rama establece `jdk.version` 1.8. Los resultados a continuación se produjeron con Zulu 1.8.0_432 (arm64). Apunte `JAVA_HOME` a una instalación de JDK 8 antes de ejecutar cualquier comando en esta sección: |
| Build | Apache Maven, se requiere acceso a la red (POM padre `org.apache:apache:38`) |
| Baseline | `origin/1.13.x` |
| Configuración | `userDnTemplate = uid={0},ou=users,dc=mycompany,dc=com` |
| Servidor de directorio | **No requerido para §4.3-§4.4** — el defecto está en la construcción del DN, antes de cualquier llamada de red, por lo que esos pasos simulan `LdapContextFactory`. §4.5 además reproduce la ruta completa contra un servidor LDAP **real** sobre HTTP. |

Establecer `JAVA_HOME` a una instalación de JDK 8:```bash
# macOS
export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)

# Linux (path varies by distribution and vendor)
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64

# Windows (cmd)
set JAVA_HOME=C:\Program Files\Zulu\zulu-8

Confirma con mvn -v, que informa el JDK que utilizará Maven. Los comandos a continuación asumen que esto ya está configurado.

4.2 Obtener la línea base sin corregir```bash

git clone https://github.com/apache/shiro.git cd shiro git checkout -b repro origin/1.13.x

root@kitploit:~
### 4.3 Observar el defecto directamente

Este es el disparador mínimo, independiente de la compilación de Shiro. Escribe `Repro.java` en un directorio temporal:```java
import javax.naming.ldap.LdapName;

public class Repro {
    // reproduces DefaultLdapRealm.getUserDn line-for-line
    static String getUserDn(String prefix, String principal, String suffix) {
        StringBuilder sb = new StringBuilder(prefix.length() + principal.length() + suffix.length());
        sb.append(prefix);
        sb.append(principal);
        sb.append(suffix);
        return sb.toString();
    }

    public static void main(String[] args) throws Exception {
        String prefix = "uid=";
        String suffix = ",ou=users,dc=mycompany,dc=com";

        String benign = getUserDn(prefix, "jsmith", suffix);
        System.out.println("benign : " + benign + "  -> RDNs=" + new LdapName(benign).size());

        String crafted = getUserDn(prefix, "jsmith,ou=admins", suffix);
        System.out.println("crafted: " + crafted + "  -> RDNs=" + new LdapName(crafted).size());
    }
}

Ejecútalo:```bash javac Repro.java && java Repro

root@kitploit:~
Salida observada:```
benign : uid=jsmith,ou=users,dc=mycompany,dc=com  -> RDNs=4
crafted: uid=jsmith,ou=admins,ou=users,dc=mycompany,dc=com  -> RDNs=5

El recuento de componentes cambia de 4 a 5. El valor enviado se ha convertido en estructura de nombre.

4.4 Reproducir a través de la propia API de Shiro

Desde la raíz del repositorio en la línea base sin corregir, aplique las pruebas de la §6.3 y ejecute:```bash mvn -B clean verify

root@kitploit:~
La compilación se detiene en `Apache Shiro :: Core` con las pruebas de verificación fallando — véase §6.1 para la salida completa capturada:```
DefaultLdapRealmTest   Tests run: 16, Failures: 4, Errors: 0, Skipped: 0
JndiLdapRealmTest      Tests run: 16, Failures: 4, Errors: 0, Skipped: 0

4.5 Reproducción de extremo a extremo sobre HTTP contra un servidor de directorio en vivo

Reproducido a través de todo el stack — una solicitud HTTP real, un contenedor de servlets real, el propio FormAuthenticationFilter de Shiro y un servidor LDAP real. Nada está simulado en ninguna capa.

Fixture de directorio — un contenedor privilegiado anidado, una disposición común del mundo real:``` dc=mycompany,dc=com └── ou=users ├── uid=jsmith userPassword: userpass (ordinary account) └── ou=admins └── uid=jsmith userPassword: adminpass (privileged account)

root@kitploit:~
#### 4.5.1 Compilación afectada

**Solicitud**```http
POST /login HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded

username=jsmith%2Cou%3Dadmins&password=adminpass

Respuesta```http HTTP/1.1 302 Found Location: http://127.0.0.1/;jsessionid=node0qu3v2n612vy71alsuyir2bgzz1.node0

root@kitploit:~
**Bind DN que recibió el directorio**```
uid=jsmith,ou=admins,ou=users,dc=mycompany,dc=com

El 302 es la redirección exitosa de FormAuthenticationFilter: la solicitud autenticada. El DN lleva cinco componentes donde la plantilla define cuatro, y la entrada alcanzada es la privilegiada bajo ou=admins.

Control — el mismo nombre de usuario con la contraseña de la cuenta ordinaria```http POST /login HTTP/1.1 Content-Type: application/x-www-form-urlencoded

username=jsmith%2Cou%3Dadmins&password=userpass

root@kitploit:~
| `--no-color` | Desactiva la salida con color |
| `--debug` | Habilita el registro de depuración |
| `--verbose` | Habilita el registro detallado |
| `--silent` | Suprime toda la salida excepto los resultados |
| `--output <file>` | Escribe la salida en un archivo |
| `--format <format>` | Formato de salida (json, yaml, table) |
| `--timeout <seconds>` | Tiempo de espera de la solicitud |
| `--retry <count>` | Número de reintentos |
| `--proxy <url>` | URL del proxy |
| `--headers <headers>` | Encabezados HTTP personalizados |
| `--cookies <cookies>` | Cookies personalizadas |
| `--user-agent <ua>` | Cadena de User-Agent personalizada |
| `--follow-redirects` | Seguir redirecciones HTTP |
| `--max-redirects <count>` | Número máximo de redirecciones |
| `--insecure` | Omitir la verificación del certificado TLS |
| `--rate-limit <rps>` | Limitar las solicitudes por segundo |
| `--concurrency <count>` | Número de solicitudes concurrentes |
| `--threads <count>` | Número de hilos |
| `--wordlist <file>` | Archivo de lista de palabras |
| `--extensions <ext>` | Extensiones de archivo a probar |
| `--status-codes <codes>` | Códigos de estado HTTP a mostrar |
| `--exclude-status <codes>` | Códigos de estado HTTP a excluir |
| `--recursive` | Habilitar el escaneo recursivo |
| `--depth <count>` | Profundidad máxima de recursión |
| `--random-agent` | Usar un User-Agent aleatorio |
| `--delay <ms>` | Retardo entre solicitudes en milisegundos |
| `--config <file>` | Archivo de configuración |
| `--profile <name>` | Perfil de configuración a usar |
| `--list-profiles` | Listar los perfiles de configuración disponibles |
| `--save-profile <name>` | Guardar la configuración actual como un perfil |
| `--delete-profile <name>` | Eliminar un perfil de configuración |
| `--show-config` | Mostrar la configuración actual |
| `--validate-config` | Validar el archivo de configuración |
| `--generate-config` | Generar un archivo de configuración predeterminado |
| `--version` | Mostrar la información de la versión |
| `--help` | Mostrar el mensaje de ayuda |

### Ejemplos

```bash
# Escaneo básico
./tool -u https://example.com

# Escaneo con lista de palabras
./tool -u https://example.com -w wordlist.txt

# Escaneo con extensiones
./tool -u https://example.com -w wordlist.txt -e php,html,js

# Escaneo con concurrencia
./tool -u https://example.com -w wordlist.txt -c 50

# Escaneo con proxy
./tool -u https://example.com -w wordlist.txt -p http://127.0.0.1:8080

# Escaneo con encabezados personalizados
./tool -u https://example.com -H "Authorization: Bearer token"

# Escaneo con cookies
./tool -u https://example.com -b "session=abc123"

# Escaneo con salida JSON
./tool -u https://example.com -o results.json -f json

# Escaneo con salida YAML
./tool -u https://example.com -o results.yaml -f yaml

# Escaneo con salida de tabla
./tool -u https://example.com -o results.txt -f table

# Escaneo con tiempo de espera
./tool -u https://example.com -t 30

# Escaneo con reintentos
./tool -u https://example.com -r 3

# Escaneo con limitación de velocidad
./tool -u https://example.com --rate-limit 10

# Escaneo con seguimiento de redirecciones
./tool -u https://example.com --follow-redirects

# Escaneo con omisión de la verificación TLS
./tool -u https://example.com --insecure

# Escaneo con User-Agent aleatorio
./tool -u https://example.com --random-agent

# Escaneo con archivo de configuración
./tool -u https://example.com --config config.yaml

# Escaneo con perfil
./tool -u https://example.com --profile default

# Escaneo con depuración
./tool -u https://example.com --debug

# Escaneo con salida detallada
./tool -u https://example.com --verbose

# Escaneo con salida silenciosa
./tool -u https://example.com --silent

Configuración

La herramienta admite un archivo de configuración en formato YAML:

root@kitploit:~
# config.yaml
target: https://example.com
wordlist: wordlist.txt
extensions:
  - php
  - html
  - js
concurrency: 50
timeout: 30
retry: 3
rate_limit: 10
follow_redirects: true
insecure: false
random_agent: false
output: results.json
format: json
headers:
  Authorization: "Bearer token"
  X-Custom-Header: "value"
cookies:
  session: "abc123"
proxy: http://127.0.0.1:8080
user_agent: "Mozilla/5.0"
status_codes:
  - 200
  - 301
  - 302
  - 403
exclude_status:
  - 404
recursive: true
depth: 3
delay: 100
threads: 10

Perfiles

La herramienta admite múltiples perfiles de configuración:

root@kitploit:~
# Listar perfiles
./tool --list-profiles

# Guardar perfil
./tool --save-profile myprofile

# Eliminar perfil
./tool --delete-profile myprofile

# Usar perfil
./tool --profile myprofile -u https://example.com

Solución de problemas

Problemas comunes

  1. Errores de conexión

    • Verifique la conectividad de red
    • Verifique la URL de destino
    • Verifique la configuración del proxy
  2. Errores de tiempo de espera

    • Aumente el valor del tiempo de espera
    • Reduzca la concurrencia
    • Verifique el rendimiento del servidor de destino
  3. Errores de certificado TLS

    • Use --insecure para omitir la verificación
    • Actualice los certificados CA
    • Verifique la configuración de TLS
  4. Errores de permisos

    • Ejecute con privilegios elevados
    • Verifique los permisos de archivo
    • Verifique la configuración del firewall

Modo de depuración

Habilite el registro de depuración para solucionar problemas:

root@kitploit:~
./tool -u https://example.com --debug

Modo detallado

Habilite el registro detallado para obtener información más detallada:

root@kitploit:~
./tool -u https://example.com --verbose

Consideraciones de rendimiento

  • Use una concurrencia adecuada según el rendimiento del servidor de destino
  • Use la limitación de velocidad para evitar sobrecargar el servidor de destino
  • Use el modo silencioso para reducir la sobrecarga de salida
  • Use la salida JSON para facilitar el análisis
  • Use perfiles para guardar configuraciones de uso frecuente

Consideraciones de seguridad

  • Obtenga siempre la autorización adecuada antes de realizar pruebas
  • No use la herramienta contra objetivos sin permiso
  • Tenga en cuenta las leyes y regulaciones locales
  • Use HTTPS para las comunicaciones
  • Verifique los certificados TLS en producción
  • No exponga información confidencial en los registros

Contribución

¡Agradecemos las contribuciones! Consulte CONTRIBUTING.md para obtener más detalles.

Licencia

Este proyecto está licenciado bajo la Licencia MIT: consulte el archivo LICENSE para obtener más detalles.

Descargo de responsabilidad

Esta herramienta está destinada únicamente a pruebas de seguridad autorizadas y fines educativos. Los autores no son responsables de ningún uso indebido o daño causado por esta herramienta. Use esta herramienta de manera responsable y ética.

Agradecimientos

  • ProjectDiscovery
  • OWASP
  • SecLists
  • PayloadsAllTheThings

Contacto

  • Sitio web: https://example.com
  • Twitter: @example
  • Correo electrónico: [email protected]

Registro de cambios

Consulte CHANGELOG.md para obtener el historial de cambios.

Hoja de ruta

  • Agregar soporte para más protocolos
  • Agregar soporte para más formatos de salida
  • Agregar soporte para más métodos de autenticación
  • Agregar soporte para más opciones de configuración
  • Agregar soporte para más opciones de rendimiento
  • Agregar soporte para más opciones de seguridad
  • Agregar soporte para más opciones de integración
  • Agregar soporte para más opciones de extensibilidad
  • Agregar soporte para más opciones de personalización
  • Agregar soporte para más opciones de automatización

Estado

Estado Versión Licencia Plataforma Lenguaje Compilación Cobertura Descargas Estrellas Bifurcaciones Problemas Solicitudes de extracción Colaboradores Última confirmación Lanzamiento ```http HTTP/1.1 200 OK

root@kitploit:~
Rechazado. Esto es lo que establece la suplantación en lugar de una coincidencia: el nombre de usuario manipulado se autentica con la contraseña de la **cuenta privilegiada** y falla con la del usuario común, por lo que las credenciales que se están verificando pertenecen a una entrada de directorio diferente de la que abordan las plantillas.

#### 4.5.2 Compilación corregida — solicitudes idénticas

| Solicitud | Afectado | Corregido |
|---|---|---|
| `username=jsmith&password=userpass` | `302` autenticado | `302` autenticado |
| `username=jsmith%2Cou%3Dadmins&password=adminpass` | **`302` autenticado** | **`200` rechazado** |
| `username=jsmith%2Cou%3Dadmins&password=userpass` | `200` rechazado | `200` rechazado |

Bind DN recibido por el directorio para el nombre de usuario manipulado:```
affected   : uid=jsmith,ou=admins,ou=users,dc=mycompany,dc=com      (5 components)
remediated : uid=jsmith\,ou\=admins,ou=users,dc=mycompany,dc=com    (4 components)

Los inicios de sesión ordinarios no cambian — la fila 1 se autentica de forma idéntica en ambas compilaciones, y el directorio recibe uid=jsmith,ou=users,dc=mycompany,dc=com.

Ambas ejecuciones usaron el mismo arnés y el mismo fixture; solo difería shiro-core en el classpath. La clase que realmente se cargó se confirmó con -verbose:class para cada ejecución.


5. Detalles de remediación y código de remediación

5.1 Corrección principal

Codificar el principal como un único valor de atributo DN antes de la sustitución, usando el propio codificador RFC 2253 del JDK, javax.naming.ldap.Rdn.escapeValue. Este es el mismo mecanismo que el JDK usa para construir nombres, por lo que su salida es, por construcción, consistente con el analizador que la consume.

Archivo: `core/src/main/java/org/apache/shiro/realm/ldap/DefaultLdapRealm.java````diff @@ -35,6 +35,7 @@ import org.slf4j.LoggerFactory; import javax.naming.AuthenticationNotSupportedException; import javax.naming.NamingException; import javax.naming.ldap.LdapContext; +import javax.naming.ldap.Rdn;

/**

  • An LDAP {@link org.apache.shiro.realm.Realm Realm} implementation utilizing Sun's/Oracle's @@ -239,11 +240,14 @@ public class DefaultLdapRealm extends AuthorizingRealm {

    root@kitploit:~
     int prefixLength = prefix != null ? prefix.length() : 0;
     int suffixLength = suffix != null ? suffix.length() : 0;
    
  • root@kitploit:~
       StringBuilder sb = new StringBuilder(prefixLength + principal.length() + suffixLength);
    
  • root@kitploit:~
       //the principal is a single attribute value within the resulting name, so it is encoded
    
  • root@kitploit:~
       //to keep any characters that are significant in a Distinguished Name within that value:
    
  • root@kitploit:~
       String value = Rdn.escapeValue(principal);
    
  • root@kitploit:~
       StringBuilder sb = new StringBuilder(prefixLength + value.length() + suffixLength);
       if (prefixLength > 0) {
           sb.append(prefix);
       }
    
  • root@kitploit:~
       sb.append(principal);
    
  • root@kitploit:~
       sb.append(value);
       if (suffixLength > 0) {
           sb.append(suffix);
       }
    
root@kitploit:~
**Qué impone el hunk.** `Rdn.escapeValue` escapa con barra invertida el conjunto reservado de la RFC 2253
(`\ , = + < > # ; "`) más los espacios en blanco iniciales y finales. Después de esto, el texto
sustituido solo puede ocupar un valor de RDN: el analizador lee los caracteres escapados como contenido, por lo que
el número de componentes del resultado queda fijado por la plantilla y no puede ser influenciado por el
principal. La sugerencia de capacidad del `StringBuilder` se actualiza a la longitud codificada — un detalle de dimensionamiento,
no de comportamiento.

**Ubicación.** La codificación se sitúa *después* del retorno temprano para el caso de plantilla no configurada.
Esto es deliberado: en ese modo el llamador proporciona un DN completo y codificarlo
lo corrompería. Ambas ramas mantienen sus contratos existentes.

### 5.2 Cambio de comportamiento para llamadores legítimos

Esta corrección **cambia la cadena de DN** producida para cualquier principal que contenga un carácter
reservado. Tres consecuencias que vale la pena enunciar claramente:

1. **Los inicios de sesión antes rotos ahora funcionan.** Los nombres de usuario que contenían una barra invertida o comenzaban
   con `#` producían un DN que no era un nombre bien formado, por lo que no se podía intentar ningún bind en
   absoluto. Ahora se codifican a un DN válido e intentarán un bind. Los sitios pueden ver cuentas que comienzan
   a autenticarse cuando antes no podían — una corrección, pero un cambio visible. Los nombres de usuario
   que contenían `,`, `;`, `+` o `=` no eran rechazados antes; resolvían a la entrada
   incorrecta, y ahora resuelven a la deseada.
2. **Los DN de bind enviados al directorio difieren** para los nombres de usuario afectados. Los registros del lado del directorio,
   las pistas de auditoría y cualquier extracción de registros que coincida con cadenas de DN literales verán formas
   escapadas (`uid=jsmith\,ou\=admins,...`). No se requiere ningún cambio de configuración.
3. **Las implementaciones que dependían del comportamiento antiguo para construir DN de múltiples componentes a partir del
   campo de nombre de usuario se romperían.** Esta no es una configuración soportada — la plantilla
   existe para definir la estructura — pero es el único riesgo de migración, y es el mismo
   comportamiento que la corrección existe para eliminar.

`getUserDnTemplate()` está implementado como `getUserDn("{0}")`. `{` y `}` no son reservados en
la RFC 2253, por lo que el valor de retorno del accesor no cambia. Confirmado por el
`testUserDnTemplate` preexistente, que pasa sin modificaciones.

---

## 6. Verificación y pruebas

### 6.1 Antes — línea base, sin corregir

Rama `1.13-CVE-2026-49268`, JDK Zulu 1.8.0_432. Comando según §4.4.```
DefaultLdapRealmTest   Tests run: 16, Failures: 4, Errors: 0, Skipped: 0
JndiLdapRealmTest      Tests run: 16, Failures: 4, Errors: 0, Skipped: 0

Salida de fallo capturada — esta es la evidencia, y no es recuperable una vez que la corrección está en su lugar:``` testGetUserDnPreservesTemplateStructure:238 User DN gained or lost components relative to the template: uid=jsmith,ou=admins,ou=users,dc=mycompany,dc=com expected:<4> but was:<5>

testGetUserDnPreservesTemplateStructureForReservedCharacters:262 Component count changed for principal [jsmith,ou=admins]: uid=jsmith,ou=admins,ou=users,dc=mycompany,dc=com expected:<4> but was:<5>

testGetUserDnEncodesSubstitutedValue:280 expected:<uid=jsmith[,ou]=admins,ou=users,dc=...> but was:<uid=jsmith[,ou]=admins,ou=users,dc=...>

testUserDnTemplateSubstitutionPreservesStructure:300 Unexpected method call LdapContextFactory.getLdapContext("uid=jsmith,ou=admins,ou=users,dc=mycompany,dc=com", ...) expected: LdapContextFactory.getLdapContext("uid=jsmith,ou=admins,ou=users,dc=mycompany,dc=com", ...) expected: 1, actual: 0

root@kitploit:~
`testGetUserDnLeavesOrdinaryPrincipalUnchanged` **pasó en la línea base**, como se pretendía — es
una protección contra la sobrecorrección, no una prueba de validación.

### 6.2 Después — con la corrección aplicada

Mismo comando, mismo JDK:```
DefaultLdapRealmTest   Tests run: 16, Failures: 0, Errors: 0, Skipped: 0
JndiLdapRealmTest      Tests run: 16, Failures: 0, Errors: 0, Skipped: 0

Todas las cuatro pruebas de verificación ahora pasan en ambas clases. Volver a ejecutar §4.3 con la corrección aplicada produce RDNs=4 para el principal elaborado.

6.3 Pruebas añadidas

Archivo: core/src/test/java/org/apache/shiro/realm/ldap/DefaultLdapRealmTest.java (+124 líneas, 0 eliminaciones). Dado que JndiLdapRealmTest extends DefaultLdapRealmTest, cada prueba a continuación se ejecuta contra ambos realms — 10 ejecuciones de 5 métodos.

Nota de diseño. Las aserciones estructurales analizan el resultado con javax.naming.ldap.LdapName y comparan los recuentos de componentes y el valor hoja convertido de ida y vuelta, en lugar de afirmar una cadena escapada esperada. Por lo tanto, las pruebas verifican la propiedad requerida real y no presuponen Rdn.escapeValue como la implementación — un codificador correcto alternativo seguiría pasando.

Comando:```bash mvn -B clean verify

root@kitploit:~
### 6.4 Regresión — compilación completa

La compilación completa del reactor pasa con **sin flags y sin omisiones**:```
mvn -B clean verify

BUILD SUCCESS — 907 pruebas, 0 fallos, 0 errores, 3 omitidas, en todo el reactor. Todas las puertas están activas en esta ejecución: pruebas unitarias (surefire), pruebas de integración (failsafe), la auditoría de licencias Apache RAT, maven-enforcer y japicmp.

Las 3 omisiones son @Ignore preexistentes en módulos no relacionados con este cambio; core — el único módulo modificado — no omite nada.

Referencias

  • Registro CVE (API de MITRE): https://cveawg.mitre.org/api/cve/CVE-2026-49268
  • Anuncio upstream: https://lists.apache.org/thread/svszql3od8td7hn6conyj2oq70v53b5s
  • Informes de seguridad de Apache Shiro: https://shiro.apache.org/security-reports.html
  • RFC 2253 / RFC 4514 — Representación en cadena LDAP de Distinguished Names
  • RFC 4515 — Representación en cadena de filtros de búsqueda LDAP

Contacto

Autor de Investigación y Remediación de Vulnerabilidades de Seguridad: Jinwoo Hwang (https://JinwooHwang.com)

Descargar herramienta
CampoValor
CVECVE-2026-49268 — Apache Shiro: Inyección de DN LDAP en DefaultLdapRealm
ID de CWECWE-90 — Neutralización incorrecta de elementos especiales utilizados en una consulta LDAP ('Inyección LDAP')
CVSS v4.08.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.19.1 CRÍTICO — CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
Versiones afectadasorg.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 en2.2.1 y 3.0.0-alpha-2
Referenciahttps://lists.apache.org/thread/svszql3od8td7hn6conyj2oq70v53b5s
PrincipalResultado de la línea baseEfecto
jsmith,ou=adminsanaliza, 5 RDNestructura alterada — se interpone un contenedor
jsmith;ou=adminsanaliza, 5 RDNestructura alterada — ; es un separador de RDN según RFC 1779
jsmith+uid=adminanaliza, 4 RDNse convierte en un RDN multivaluado; uid gana un segundo valor
jsmith=adminanaliza, 4 RDNvalor corrompido
quo"te, angle<br>ackets, leadingSpace, trailingSpace analizan, 4 RDNvalor corrompido
back\slashIllegalArgumentExceptionmalformado — no se puede intentar el bind
#leadingNumberSignIllegalArgumentExceptionmalformado — no se puede intentar el bind
CapaComponente
Cliente HTTPHttpURLConnection, POST de formulario sin procesar
Contenedor de servletsJetty 9.4.58.v20250814 embebido
Filtro de seguridadShiroFilter + EnvironmentLoaderListener, authc (FormAuthenticationFilter)
RealmDefaultLdapRealm, userDnTemplate = uid={0},ou=users,dc=mycompany,dc=com
DirectorioUnboundID InMemoryDirectoryServer, instrumentado para registrar cada bind DN
Docker
Homebrew
Snap
AUR
Chocolatey
Scoop
Winget
APT
YUM
DNF
Pacman
Portage
Nix
Guix
Flatpak
AppImage
Snapcraft
Go
Python
Node
Rust
Java
Ruby
PHP
Perl
Lua
R
Julia
Scala
Kotlin
Swift
Dart
Elixir
Erlang
Haskell
OCaml
F#
Clojure
Lisp
Scheme
Racket
Groovy
Crystal
Nim
Zig
V
D
Ada
Fortran
COBOL
Assembly
WebAssembly
Solidity
Move
Cairo
Vyper
Rust
Go
Python
Node
Java
Ruby
PHP
Perl
Lua
R
Julia
Scala
Kotlin
Swift
Dart
Elixir
Erlang
Haskell
OCaml
F#
Clojure
Lisp
Scheme
Racket
Groovy
Crystal
Nim
Zig
V
D
Ada
Fortran
COBOL
Assembly
WebAssembly
Solidity
Move
Cairo
Vyper
PruebaVerificaLínea base
testGetUserDnLeavesOrdinaryPrincipalUnchangedUn principal ordinario produce el DN exacto esperado, 4 RDNs, valor intacto. Protege contra el escape excesivo.pasó (guarda)
testGetUserDnPreservesTemplateStructurePara jsmith,ou=admins el DN todavía tiene 4 componentes y el principal sobrevive como un valor de atributo.falló
testGetUserDnPreservesTemplateStructureForReservedCharactersLo mismo, en los 10 principales con caracteres reservados; también falla la prueba si el DN no es un nombre bien formado.falló
testGetUserDnEncodesSubstitutedValueEl valor sustituido se codifica de tal manera que se convierte de ida y vuelta al principal enviado.falló
testUserDnTemplateSubstitutionPreservesStructureDe extremo a extremo a través de getAuthenticationInfo: el DN entregado a LdapContextFactory conserva la estructura de la plantilla.falló
PuertaResultado
Pruebas unitarias, reactor completo907 ejecutadas, 0 fallos, 0 errores, 3 omitidas
Módulo core por sí solo321 ejecutadas, 0 fallos, 0 errores, 0 omitidas
Pruebas LDAP (DefaultLdapRealmTest + JndiLdapRealmTest)32 ejecutadas, 0 fallos
Pruebas de integración (failsafe)ejecutadas en todo el reactor, sin fallos
Auditoría de licencias Apache RATNo aprobadas: 0, desconocidas: 0
maven-enforcersin violaciones
japicmpsin incompatibilidades reportadas