
CVE-2026-49268 — Análise e Remediação de uma Vulnerabilidade de Bypass de Autenticação por Injeção LDAP
| Branch | 1.13-CVE-2026-49268 |
| Autor | Jinwoo Hwang (https://JinwooHwang.com) |
Este branch (1.13-CVE-2026-49268) contém uma remediação de segurança abrangente de uma Vulnerabilidade de Bypass de Autenticação por Injeção LDAP na versão 1.13 do Apache Shiro.
Descrição Oficial do NVD
Um atacante remoto pode injetar caracteres especiais de LDAP na construção do Distinguished Name (DN) na classe DefaultLdapRealm. A entrada de nome de usuário fornecida pelo usuário é concatenada diretamente no template de DN do LDAP sem qualquer escape de caracteres especiais da RFC 2253. Isso permite que um atacante manipule a estrutura do DN usada para autenticação de bind LDAP, potencialmente contornando a autenticação ou se passando por outros usuários. Este problema afeta todas as versões do Apache Shiro até a 2.2.0, e a 3.0.0-alpha-1 ao usar o DefaultLdapRealm.
| Campo | Valor |
|---|---|
| CVE | CVE-2026-49268 — Apache Shiro: Injeção de DN LDAP no DefaultLdapRealm |
| CWE ID | CWE-90 — Neutralização Incorreta de Elementos Especiais usados em uma Consulta LDAP ('Injeção 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 |
| Versões afetadas | org.apache.shiro:shiro-core 0 até 2.2.0 inclusive; 3.0.0-alpha-0 até 3.0.0-alpha-1 inclusive |
| Corrigido upstream em | 2.2.1 e 3.0.0-alpha-2 |
| Referência | https://lists.apache.org/thread/svszql3od8td7hn6conyj2oq70v53b5s |
O Apache Shiro pode autenticar usuários em um diretório LDAP. Para isso, ele transforma um
nome de usuário submetido em um Distinguished Name — o endereço do diretório para a entrada
daquele usuário — inserindo o nome de usuário em um template configurado, por exemplo
uid={0},ou=users,dc=mycompany,dc=com.
Nas versões afetadas, o nome de usuário é colado nesse template como texto bruto. Alguns caracteres de pontuação — mais importante, a vírgula — não são texto comum para um servidor de diretório; eles são a sintaxe que separa uma parte de um endereço da seguinte. Um nome de usuário contendo esses caracteres, portanto, deixa de se comportar como um valor dentro do endereço e passa a se comportar como parte do próprio endereço.
A consequência prática é que uma pessoa que faz login pode influenciar em qual entrada do diretório o Shiro tenta autenticar, em vez de apenas fornecer seu próprio nome. Em vez de ser procurado no contêiner que a implantação pretendia, a busca pode ser redirecionada para outro lugar no diretório. Dependendo de como o diretório está organizado e do que ele permite, isso pode levar à autenticação como a identidade errada, ou à autenticação bem-sucedida quando não deveria.
Perfil de risco. O que aumenta o risco: nenhum acesso prévio ou credencial é necessário — a
entrada chega na fronteira de login, que é acessível por qualquer pessoa que consiga alcançar a
aplicação; e a classe afetada é a forma padrão e documentada de conectar o Shiro ao LDAP,
portanto não é uma configuração exótica. O que reduz o risco: a implantação deve realmente usar
DefaultLdapRealm (ou JndiLdapRealm) com um template de DN configurado; implantações que
autenticam por outros meios, ou que passam um DN completo ou uma credencial não textual como um
certificado, não são afetadas. Se uma busca redirecionada produz uma autenticação utilizável
também depende do próprio layout e das regras de acesso do diretório de destino, que variam por site.
Uma segunda consequência, não relacionada à segurança, vale ser notada para o planejamento de releases: usuários cujos
nomes de usuário legítimos contêm uma barra invertida ou começam com # atualmente não conseguem fazer login
de forma alguma, porque o endereço construído para eles não é um nome bem formado e é rejeitado
de imediato. Outros sinais de pontuação não falham de imediato — eles alteram silenciosamente para qual entrada o
endereço se refere, que é o problema de segurança acima. A mesma correção resolve ambos.
core/src/main/java/org/apache/shiro/realm/ldap/DefaultLdapRealm.java, getUserDn(String),
linhas 227–250 na baseline não corrigida:```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();
}
O template é dividido uma vez, no momento da configuração, em torno do token `{0}`
(`setUserDnTemplate`, linhas 181–200), em um `prefix` e um `suffix`. No momento da autenticação
o principal é concatenado entre eles. **Nenhuma codificação é aplicada em momento algum.** Não há
nenhum auxiliar de escape em nenhum lugar de `org/apache/shiro/realm/ldap/`.
### 3.2 A invariante que era assumida mas nunca aplicada
O código ao redor trata o resultado de `getUserDn` como um Distinguished Name bem formado —
ele é passado diretamente para `LdapContextFactory.getLdapContext(...)` e daí para o JNDI como um
bind DN. Isso só é válido se o principal substituído for um *único valor de atributo*.
A concatenação de strings não consegue garantir isso. A RFC 2253 (e a RFC 4514) reservam
`,` `+` `"` `\` `<` `>` `;` `=`, um `#` inicial, e espaços em branco iniciais/finais como sintaxe
estrutural dentro de um DN. Quando qualquer um desses aparece no principal, eles são lidos pelo
parser como estrutura, não como conteúdo. A invariante — *"o principal ocupa exatamente um valor de RDN"* —
era assumida por todos os consumidores a jusante e não era aplicada por nenhum.