Une récente analyse par Foxglove Security d'une conférence "AppSecCali: Marshalling Pickles" (video, slides) donnée par @frohoff et @gebl en Janvier 2015, a confirmé de multiples failles 0-day, exploitables à distance, pour les applications Java qui désérialisent des objets provenant de sources réseau non-sécurisées et qui utilisent des librairies telles que Apache Commons Collections, Groovy ou Spring. Puisqu'un certain nombre de serveurs d'application supportent la désérialisation d'objets de manière transparente depuis des flux de donnée ou des cookies, il est trivial de passer du code malveillant au travers d'une requête HTTP vers un serveur qui peut très bien être derrière un pare-feu ou des scanners de sécurité.
Le code prouvant le concept sur Apache Commons Collections 3.x est supposé fonctionner aussi sur JBoss, Jenkins, Weblogic et WebSphere. Bien que ce ne soit pas aussi trivial à exploiter que la vulnérabilité Apache Struts de 2014, le code est aussi puissant et la vulnérabilité susceptible d'être trouvée à bien plus d'endroits que détecté actuellement.
La vulnérabilité dérive du fait que désérialiser un objet Java peut instancier un nom de classe arbitraire avec des données arbitraires. Dans le cas des Commons Collection Apache 3.x, cela inclut une classe Transformer qui autorise la conversion d'un type d'entrée vers un autre type d'objet. Ce processus est appelé automatiquement par les routines de désérialisation, ce qui autorise l'implémentation à fournir un objet totalement différent. Cela est combiné au ChainedTransformer et au ConstantTransformer pour instantier un objet créant un champ iConstant dans le serialized form. A son tour, celui-ci utilise une instance d'InvokerTransfomer pour exécuter une unique méthode arbitraire.
La preuve de concept a été testée par l'auteur qui a vérifié qu'avec les Apache Commons Collections 3.2.1, la vulnérabilité est réelle. Elle est spécifiquement dévelopée pour les systèmes Unix (puisque le code crée un fichier en exécutant Runtime.exec("touch /tmp/pwned")
), mais en principe, n'importe quelle commande peut être exécutée sur n'importe quel système d'exploitation. La preuve de concept cible aussi spécifiquement Apache Commons Collections 3.x, car Apache Commons 4.x utilise un namespace différent (org.apache.commons.collections4
). Il existe des exploits alternatifs contre Apache Commons Collections 4.x dans le repository en amont ainsi que pour Groovy 2.x (inférieur à la 2.4.4) et Spring 4.x.
Mise à jour : Guillaume Laforge a contacté InfoQ pour confirmer que la vulnérabilité dans Groovy avait été corrigée en 2.4.4 sous CVE-2015-3253 et GROOVY-7504 plus tôt cette année, lors de la première release au sein de la Fondation Apache. Puisque le problème est présent dans toutes les versions plus anciennes de Groovy, les utilisateurs sont appelés à mettre à jour vers la release actuelle d'Apache pour éviter ce problème spécifique.
Dans tous les cas, les données fournies par l'utilisateur permettent à une chaîne arbitraire d'objets d'être construits, ce qui résulte au final à la désérialisation d'une séquence d'octets et à l'exécution du code qu'ils représentent. Il s'agit d'une charge d'attaque sous la forme d'une classe ayant un initialiseur qui inclut Runtime.exec()
sur une commande fournie par l'utilisateur, et dissimulée dans une série d'objets qui, une fois exécutés, déclencheront la charge. Bien que la preuve de concept utilise un simple Runtime.exec()
pour les classes Apache Collections, une attaque alternative contre une copie du JDK de com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
(qui est présente y compris dans les toutes dernières versions d'OpenJDK 8) peut aussi être exploitée ; cela est démontré dans les attaques sur Spring au travers d'un mécanisme d'injection de dépendance et un fournisseur de type (type provider).
Malheureusement, il n'est pas possible de se protéger de ces vulnérabilités efficacement et facilement. Elles s'appliquent à un grand nombre d'applications Java (utilisant ou ayant Groovy dans le classpath, ou Spring 4.x, ou Apache Commons Collections 3.x comme 4.x). De plus, il n'y a pas de moyen facile d'éviter l'usage de la sérialisation d'objets dans une JVM, car elle est utilisée potentiellement par beaucoup d'opérations Java.
Comme la preuve de concept le montre, un grand nombre de serveurs d'applications Java sont vulnérables à l'exécution de code distant au travers de leur capacité à envoyer des données via un port réseau. Jenkins, par exemple, utilise la sérialisation objet pour l'interface Jenkins CLI (ligne de commande) et est donc vulnérable à n'importe quel utilisateur distant pouvant atteindre ce port. CloudBees, un des principaux sponsors de Jenkins, a récemment divulgué un communiqué de sécurité recommendant la désactivation immédiate du service de ligne de commande Jenkins pour éviter cette vulnérabilité, et note que toutes les installations Cloudbees Jenkins auto-hébergées sont vulnérables. Dans le cas de WebSphere, l'attaque est démontrée au travers d'une requête SOAP vers le AdminService
et dans les faits, tout port javax.management
(qui utilise la sérialisation d'objets distants et les librairies buggées) est potentiellement vulnérable. L'exploitation de la faille sur JBoss utilise le même chemin en postant une requête sur le service JMXInvoker
. Tout serveur faisant du RMI est aussi potentiellement vulnérable - bien qu'en pratique, les ports RMI ouverts vers l'internet sont en soit une mauvaise pratique de sécurité.
Comme le note Foxglove Security, il n'y a pas de moyen efficace de déterminer ce que représentent les objets lorsqu'ils transitent sur le réseau. Les objets sérialisés par Java commencent par la séquence 0xaced0005
, ou rO0
s'ils sont encodés en base64. Chercher des chaînes de caractère pour les classes utilisées par les différents exploits peut servir à trouver des signatures, puisque les noms de classe sont représentés par des chaînes UTF-8 dans les classes elles-mêmes :
- org/apache/commons/collections/Transformer
- org.apache.commons.collections.Transformer
- org.apache.commons.collections.functors.InvokerTransformer
- org/apache/commons/collections4/functors/InvokerTransformer
- org.apache.commons.collections4.functors.InvokerTransformer
- org/codehaus/groovy/runtime/ConvertedClosure
- org.codehaus.groovy.runtime.ConvertedClosure
- com/sun/org/apache/xalan/internal/xsltc/trax/TemplatesImpl
- com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
Il n'y a pas de raison pratique pour que l'une de ces classes soit transmise au travers d'un objet sérialisé dans un cookie ou tout autre flux binaire. Il se pourrait que ces classes puissent être supprimées sans affecter de réelles fonctionalités, mais les logiciels de surveillance de réseau pourraient aussi examiner les en-têtes HTTP et les cookies pour rechercher des séquences comprenant ces chaînes dans des objets encodés en base64 préfixés par rO0
. Malheureusement, les réponses peuvent être compressées ou HTTP/2 utilisé, ce qui autoriserait les charges à provenir de différentes requêtes enchaînées.
A cause du nombre d'endroits dans lesquels la désérialisation peut se produire depuis des données réseau (ou au travers de protocoles réseaux propriétaires), les brèches pourraient être difficiles à combler. Comme pour tous les mécanismes de sécurité, valider les entrées est la façon clé de prévenir des vulnérabilités. Dans le cas de la désérialisation d'objet Java, son usage devrait être purement et simplement évité, en y préférant des formats personalisés de sérialisation comme l'encoding JSON ou XML, ou l'usage d'une librairie spécifique comme Protobuf ou Thrift. Notez que l'exploitation de la faille sur WebSphere via SOAP utilise XML pour passer une charge encodée en base64, qui est ensuite sujette à sérialisation. Même l'utilisation de protocoles connus pour le transfert des données ne prémunit pas des exploitations de faille potentielles.