
shiro-cve-2020-17523 análise de duas posturas de bypass da vulnerabilidade e ambiente de vulnerabilidade correspondente
O Apache Shiro é um framework de segurança Java poderoso e fácil de usar, que executa autenticação, autorização, gerenciamento de senhas e sessões. Usando a API de fácil compreensão do Shiro, você pode rapidamente e facilmente proteger qualquer aplicação, desde as menores aplicações móveis até as maiores aplicações web e empresariais.
Quando combinado com o Spring, sob certas regras de correspondência de permissões, um atacante pode contornar a autenticação construindo pacotes de requisição HTTP especiais.
Versões afetadas: Apache Shiro < 1.7.1
Shiro 1.7.0
https://github.com/jweny/shiro-cve-2020-17523 Os ambientes de vulnerabilidade para ambas as técnicas já foram atualizados.
Técnica 1:
http://127.0.0.1:8080/admin/%20 ou http://127.0.0.1:8080/admin/%20/
Usando caracteres em branco como espaços, é possível contornar a autenticação do Shiro.

Técnica 2:
Após comunicação com o mestre p0desta, descobrimos outra forma de exploração em um cenário especial.
http://127.0.0.1:8080/admin/%2e ou http://127.0.0.1:8080/admin/%2e/
No entanto, . (assim como /) nas regras de correspondência de caminho do Spring representa um separador de caminho e não é correspondido como um caractere comum. Portanto, acessar /admin/. retornará 404 nas condições padrão.
Mas no cenário de caminho completo ativado (setAlwaysUseFullPath(true)), a correspondência funciona normalmente.

A obtenção e correspondência de URL no Shiro ocorre em org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain.
Vamos dar uma olhada rápida neste método getChain:


Este método primeiro verifica se o requestURI termina com /. Se sim, remove a última /.
Em seguida, no loop de correspondência de caminho, primeiro verifica se a regra de caminho pathPattern termina com /. Se sim, também a remove. Depois, chama o método pathMatches() para fazer a correspondência de caminho.
*Portanto, nas duas formas de exploração, não importa se termina ou não com /, pois elas serão removidas ao passar pelo método getChain.
Vamos focar no método pathMatches():
Abra o Evaluate e calcule pathMatches("/admin/*","/admin/1") e pathMatches("/admin/*","/admin/ "). O primeiro corresponde normalmente, o segundo falha.


Inicie a depuração. Ela começa com uma longa sequência de F7 até chegar em doMatch("/admin/*","/admin/ "). Perceba que os pathDirs retornados por tokenizeToStringArray já não têm a segunda camada de diretório. Isso faz com que /admin/* e /admin não correspondam.

Seguindo o método tokenizeToStringArray, descobrimos que o parâmetro trimTokens é true quando chamado.

No método tokenizeToStringArray, quando o parâmetro trimTokens é true, ele passa por um processamento trim(), resultando na remoção do espaço. Ao retornar para getChain, a última / é removida. Consequentemente, os pathDirs retornados por tokenizeToStringArray não têm a segunda camada de diretório.

Resumindo: nas versões vulneráveis do Shiro, como o parâmetro trimTokens do método tokenizeToStringArray é true por padrão, os espaços são removidos pelo processamento trim(). Ao retornar para getChain, a última / é removida, fazendo com que /admin e /admin/* não correspondam, resultando em um bypass de autenticação. Enquanto isso, o Spring recebe o caminho de acesso /admin/%20 e processa a resposta normalmente, causando um bypass de autorização.
Vendo a segunda técnica /. e /./, você se lembrou de algum método familiar? Isso mesmo, normalize().

Simplificando:
| Condição | Exemplo |
|---|---|
| Barra invertida tratada como barra | \ -> / |
| Barra dupla tratada como barra simples |
Portanto, /admin/. é processado para /admin/./ e depois se torna /admin/.

Após o processamento em org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain, como termina com /, a última / é removida, tornando-se /admin. /admin não corresponde a /admin/*, contornando assim a autenticação do Shiro.

Neste momento, a requisição recebida pelo Spring é /admin/.. Se a correspondência de caminho completo não estiver ativada, no Spring . e / são tratados como separadores de caminho e não participam da correspondência de caminho. Portanto, não encontra o mapeamento e retorna 404.

Com a correspondência de caminho completo ativada, a URL inteira é correspondida e o Spring retorna 200.
Aqui está o código para ativar a correspondência de caminho completo:
@SpringBootApplication
public class SpringbootShiroApplication extends SpringBootServletInitializer implements BeanPostProcessor {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(SpringbootShiroApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(SpringbootShiroApplication.class, args);
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof RequestMappingHandlerMapping) {
((RequestMappingHandlerMapping) bean).setAlwaysUseFullPath(true);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
}
Com base na análise acima, existem duas causas para o bypass de autorização do Shiro:
tokenizeToStringArray não lida corretamente com espaços./ não deve estar antes da lógica de correspondência de caminho em loop.Portanto, a solução oficial é:
https://github.com/apache/shiro/commit/0842c27fa72d0da5de0c5723a66d402fe20903df
trimTokens de tokenizeToStringArray como false.
/. Modificar para primeiro corresponder o caminho original e, se falhar, então executar a lógica de remoção da última /.
Em princípio, trim() limpa todos os whitespace no início e no final de uma string. O espaço é apenas um deles. No entanto, durante os testes, descobrimos que outros whitespace, como %08, %09 e %0a, quando processados pelo Spring+Tomcat, retornam 400.
Portanto, para a primeira técnica, além do espaço, ainda não encontramos outros payloads úteis.
https://github.com/apache/shiro/commit/0842c27fa72d0da5de0c5723a66d402fe20903df
| // -> / |
| Se terminar com /. ou /.., adiciona / no final | /. -> /./ /.. -> /../ |
| Normalização de /./ | /./ -> / |
| Salto de caminho | /aaa/../bbb -> /bbb |