
Analyse de deux techniques de contournement de l'authentification pour Apache Shiro (CVE-2020-17523), avec un environnement d'exploitation reproductible et un examen détaillé de la cause racine.
Apache Shiro est un framework de sécurité Java puissant et facile à utiliser, qui assure l'authentification, l'autorisation, la gestion des mots de passe et des sessions. Grâce à l'API intuitive de Shiro, vous pouvez sécuriser rapidement et facilement toute application, de la plus petite application mobile aux plus grands réseaux et applications d'entreprise.
Lorsqu'il est utilisé avec Spring, sous certaines règles de correspondance des permissions, un attaquant peut contourner l'authentification en construisant des requêtes HTTP spéciales.
Versions concernées : Apache Shiro < 1.7.1
Shiro 1.7.0
https://github.com/jweny/shiro-cve-2020-17523 Les environnements pour les deux méthodes de contournement ont été mis à jour.
Méthode 1 :
http://127.0.0.1:8080/admin/%20 ou http://127.0.0.1:8080/admin/%20/
L'utilisation d'espaces ou de caractères blancs permet de contourner l'authentification Shiro.

Méthode 2 :
Après avoir échangé avec le collègue p0desta, une autre méthode d'exploitation dans un scénario spécifique a été découverte.
http://127.0.0.1:8080/admin/%2e ou http://127.0.0.1:8080/admin/%2e/
Mais le . (ainsi que /) est traité comme un séparateur de chemin dans les règles de correspondance de Spring, donc il n'est pas considéré comme un caractère normal pour la correspondance. Dans les conditions par défaut, accéder à /admin/. renvoie une erreur 404.
Cependant, en mode chemin complet (setAlwaysUseFullPath(true)), la correspondance fonctionne normalement.

Shiro obtient et compare l'URL dans org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain.
Regardons rapidement la méthode getChain :


Cette méthode vérifie d'abord si le requestURI se termine par /. Si c'est le cas, elle supprime le dernier /.
Ensuite, dans la boucle de correspondance du chemin, elle vérifie d'abord si le pattern pathPattern se termine par /. Si oui, elle supprime également ce dernier /. Puis elle appelle la méthode pathMatches() pour effectuer la correspondance du chemin.
Ainsi, dans les deux méthodes d'exploitation, le fait de se terminer par / ou non n'a pas d'importance, car il sera supprimé par la méthode getChain au début.
Regardons la méthode pathMatches() :
Ouvrons l'évaluateur et calculons respectivement pathMatches("/admin/*","/admin/1") et pathMatches("/admin/*","/admin/ "). La première correspond normalement, la seconde échoue.


Commençons le débogage. Après un long F7, arrivons à doMatch("/admin/*","/admin/ "). On voit que les pathDirs retournés par tokenizeToStringArray n'ont plus le deuxième niveau de répertoire. Par conséquent, /admin/* et /admin ne correspondent pas.

Suivons la méthode tokenizeToStringArray. On constate que le paramètre trimTokens est à true lors de son appel.

Dans la méthode tokenizeToStringArray, lorsque le paramètre trimTokens est à true, un traitement trim() est effectué, ce qui supprime les espaces. En retournant dans getChain, le dernier / est supprimé. Ainsi, pathDirs retourné par tokenizeToStringArray n'a pas de deuxième niveau.

Résumé : Dans la version vulnérable de Shiro, le paramètre trimTokens de la méthode tokenizeToStringArray est par défaut à true ; les espaces sont supprimés par le traitement trim(). Cela entraîne la suppression du dernier / lors du retour à getChain, donc /admin ne correspond pas à /admin/*, ce qui contourne l'authentification. Pendant ce temps, Spring reçoit le chemin d'accès /admin/%20 et répond normalement, ce qui permet le contournement.
/./En voyant la deuxième méthode /./ et /., ne pensez-vous pas à une méthode familière ? Exactement, normalize().

Traduisons brièvement :
| Condition | Exemple |
|---|---|
| Slash traité en antislash | \ -> / |
| Double slash en slash | // -> / |
| Se termine par /. ou /.., on ajoute / en fin | /. -> /./ /.. -> /../ |
| Normalisation de /./ | /./ -> / |
| Saut de chemin | /aaa/../bbb -> /bbb |
Ainsi, /admin/. est d'abord traité en /admin/./, puis devient /admin/.

Après le traitement par org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain, comme il se termine par /, on supprime le dernier / et on obtient /admin. /admin ne correspond pas à /admin/*, ce qui contourne l'authentification Shiro.

Pendant ce temps, Spring reçoit la requête /admin/.. Si le mode chemin complet n'est pas activé, dans Spring, . et / sont traités comme des séparateurs de chemin et ne participent pas à la correspondance. Par conséquent, le mapping n'est pas trouvé et une erreur 404 est renvoyée.

En activant le mode chemin complet, l'URL entière est mise en correspondance, donc Spring renvoie 200.
Voici le code pour activer le mode chemin complet :
@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);
}