
Análisis de dos técnicas de bypass de la vulnerabilidad shiro-cve-2020-17523 y el entorno de vulnerabilidad asociado.
Apache Shiro es un framework de seguridad Java potente y fácil de usar que realiza autenticación, autorización, gestión de contraseñas y sesiones. Con la API fácil de entender de Shiro, puede obtener rápida y fácilmente cualquier aplicación, desde la aplicación móvil más pequeña hasta las aplicaciones web y empresariales más grandes.
Cuando se utiliza junto con Spring, bajo ciertas reglas de coincidencia de permisos, un atacante puede evadir la autenticación mediante la construcción de paquetes de solicitud HTTP especiales.
Versiones afectadas: Apache Shiro < 1.7.1
shiro 1.7.0
https://github.com/jweny/shiro-cve-2020-17523 Los entornos de vulnerabilidad de los dos métodos ya se han actualizado.
Método uno:
http://127.0.0.1:8080/admin/%20 o http://127.0.0.1:8080/admin/%20/
El uso de caracteres en blanco como los espacios permite evadir la autenticación de Shiro.

Método dos:
Tras intercambiar ideas con el maestro p0desta, se descubrió que existe otra forma de explotación en un escenario especial.
http://127.0.0.1:8080/admin/%2e o http://127.0.0.1:8080/admin/%2e/
Sin embargo, . (y también /) en las reglas de coincidencia de rutas de Spring representan separadores de ruta y no se comparan como caracteres normales. Por lo tanto, en condiciones predeterminadas, acceder a /admin/. devuelve 404.
Pero en el escenario de ruta completa habilitada, setAlwaysUseFullPath(true) permite una coincidencia normal.

En Shiro, la obtención y coincidencia de la URL se realiza en org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain.
Primero echemos un vistazo rápido a este método getChain:


Este método primero comprueba si requestURI termina con /; si es así, elimina el último /.
Luego, en el bucle de coincidencia de rutas, primero comprueba si el patrón de ruta pathPattern termina con /; si es así, también lo elimina. Después llama al método pathMatches() para realizar la coincidencia de rutas.
Por lo tanto, en los dos métodos de explotación, no importa si se termina con /, porque se elimina en cuanto se pasa por el método getChain.
Prestemos atención al método pathMatches():
Abra Evaluate y calcule respectivamente pathMatches("/admin/*","/admin/1") y pathMatches("/admin/*","/admin/ "); el primero coincide normalmente, mientras que el segundo falla.


Comience la depuración; al iniciarla, pasará por un largo proceso de F7. Hasta llegar a doMatch("/admin/*","/admin/ "). Se puede observar que los pathDirs devueltos por tokenizeToStringArray ya no contienen la segunda capa de la ruta. Por lo tanto, /admin/* y /admin no coinciden.

Al rastrear el método tokenizeToStringArray, se descubre que, cuando se le llama, el parámetro trimTokens es true.

Y el método tokenizeToStringArray, cuando el parámetro trimTokens es true, pasa por el procesamiento de trim(), lo que provoca que los espacios se eliminen. Al volver a getChain, el último / se elimina. Por lo tanto, los pathDirs devueltos por tokenizeToStringArray no contienen la segunda capa de la ruta.

En resumen: en las versiones vulnerables de Shiro, dado que al llamar al método tokenizeToStringArray el parámetro trimTokens es true por defecto, los espacios pasan por el procesamiento de trim(), por lo que se eliminan. Al volver a getChain, el último / se elimina, de modo que /admin y /admin/* no coinciden, lo que provoca la evasión de la autenticación. Mientras tanto, la ruta de acceso que recibe Spring es /admin/%20 y devuelve la respuesta siguiendo la lógica normal, lo que resulta en la evasión de permisos.
Al ver /. y /./ del segundo método, ¿no te recuerda a un método familiar? Exacto, es normalize().

En pocas palabras:
| Condición | Ejemplo |
|---|---|
| La barra normal se procesa como barra invertida | \ -> / |
| La doble barra invertida se procesa como barra invertida |
Por lo tanto, /admin/., después de procesarse como /admin/./, se convierte en /admin/.

Después de pasar por el procesamiento de org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain, como termina en /, si es así, se elimina el último /, convirtiéndose en /admin. ``/adminno coincide con/admin/*`, por lo que se evita la autenticación de Shiro.

Mientras tanto, la solicitud que recibe Spring es /admin/.. Si la coincidencia de ruta completa no está habilitada, en Spring . y / actúan como separadores de ruta y no participan en la coincidencia de rutas. Por lo tanto, no se encuentra ningún mapping y devuelve 404.

Si la coincidencia de ruta completa está habilitada, se compara toda la URL, por lo que Spring devuelve 200.
Aquí se adjunta el código para habilitar la coincidencia de ruta completa:
@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;
}
}
Según el análisis anterior, las causas de la evasión de permisos de Shiro son dos:
tokenizeToStringArray no maneja correctamente los espacios./ no debería estar antes de la lógica del bucle de coincidencia de rutas.Por lo tanto, la solución oficial es:
https://github.com/apache/shiro/commit/0842c27fa72d0da5de0c5723a66d402fe20903df
trimTokens de tokenizeToStringArray en false.
/. Modificarla para que primero se compare con la ruta original y, si la comparación falla, se ejecute la lógica de eliminar el último /.
En teoría, trim() elimina todo el whitespace situado al principio y al final de la cadena; el espacio es solo uno de ellos. Sin embargo, en las pruebas se descubrió que otros whitespace distintos del espacio, como %08, %09, %0a, devuelven 400 al ser procesados por Spring + Tomcat.
Por lo tanto, para el primer método, aparte del espacio, aún no se han encontrado otros payloads útiles.
https://github.com/apache/shiro/commit/0842c27fa72d0da5de0c5723a66d402fe20903df
| // -> / |
| Termina en /. o /.., se añade / al final | /. -> /./ /.. -> /../ |
| Normalización de /./ | /./ -> / |
| Salto de ruta | /aaa/../bbb -> /bbb |