Il commence par rappeler que la plupart des langages fournissent des méthodes/fonctions aux utilisateurs afin qu’ils puissent exporter les données des applications sur le disque ou en faire un flux sur le réseau. C’est le processus de conversion de ces données application dans un autre format plus convenable pour le transport qui est appelé sérialisation (ou linéarisation ou marshalling). L’action d’appliquer le procédé inverse afin d’en récupérer la variable d’origine s’appelle la désérialisation (ou délinéarisation ou unmarshalling).
« Les vulnérabilités surviennent lorsque les développeurs écrivent du code qui accepte des données sérialisées des utilisateurs et tentent de les désérialiser pour les utiliser dans un programme. En fonction du langage, cela peut conduire à toutes sortes de conséquences », a-t-il expliqué, prenant le soin de préciser que la conséquence la plus intéressante selon lui est l’exécution du code à distance. Raison pour laquelle, il a décidé d’étendre le sujet en se focalisant dessus.
Il rappelle que les vulnérabilités dans la désérialisation sont tributaires du langage et explique qu’une seule de ces vulnérabilités dans les bibliothèques que votre application charge, y compris celles qu'elle n’utilise pas, peut avoir des répercussions. Il illustre ses propos par un exemple de code Java basique sur la façon dont quelqu’un pourrait utiliser la sérialisation.
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | import java.io.ObjectInputStream; import java.io.FileInputStream; import java.io.ObjectOutputStream; import java.io.FileOutputStream; public class SerializeTest{ public static void main(String args[]) throws Exception{ //Voici l’objet que nous allons sérialiser. String name = "bob"; //Ecrit la donnée sérialisée dans le fichier "name.ser" FileOutputStream fos = new FileOutputStream("name.ser"); ObjectOutputStream os = new ObjectOutputStream(fos); os.writeObject(name); os.close(); // Lit la donnée sérialisée depuis le fichier "name.ser" FileInputStream fis = new FileInputStream("name.ser"); ObjectInputStream ois = new ObjectInputStream(fis); //Lit l’objet depuis le flux de données et le convertit à nouveau en chaîne de caractère String nameFromDisk = (String)ois.readObject(); //Affiche le résultat. System.out.println(nameFromDisk); ois.close(); } } |
Code : | Sélectionner tout |
1 2 3 4 | breens@us-l-breens:~/Desktop/SerialTest$ java SerializeTest bob breens@us-l-breens:~/Desktop/SerialTest$ xxd name.ser 0000000: aced 0005 7400 0362 6f62 ....t..bob |
Maintenant, rajoutons une classe pour personnaliser la sérialisation des objets.
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | import java.io.ObjectInputStream; import java.io.FileInputStream; import java.io.ObjectOutputStream; import java.io.FileOutputStream; import java.io.Serializable; import java.io.IOException; public class SerializeTest{ public static void main(String args[]) throws Exception{ //This is the object we're going to serialize. MyObject myObj = new MyObject(); myObj.name = "bob"; //Ecrit la donnée sérialisée dans le fichier "object.ser" FileOutputStream fos = new FileOutputStream("object.ser"); ObjectOutputStream os = new ObjectOutputStream(fos); os.writeObject(myObj); os.close(); //Lit la donnée sérialisée depuis le fichier "object.ser" FileInputStream fis = new FileInputStream("object.ser"); ObjectInputStream ois = new ObjectInputStream(fis); //Lit l’objet depuis le flux de données et le convertit en chaîne de caractères MyObject objectFromDisk = (MyObject)ois.readObject(); //Print the result. System.out.println(objectFromDisk.name); ois.close(); } } class MyObject implements Serializable{ public String name; private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{ in.defaultReadObject(); this.name = this.name+"!"; } } |
Code : | Sélectionner tout |
1 2 3 4 5 6 7 | breens@us-l-breens:~/Desktop/SerialTest$ java SerializeTest bob! breens@us-l-breens:~/Desktop/SerialTest$ xxd object.ser 0000000: aced 0005 7372 0008 4d79 4f62 6a65 6374 ....sr..MyObject 0000010: cf7a 75c5 5dba f698 0200 014c 0004 6e61 .zu.]......L..na 0000020: 6d65 7400 124c 6a61 7661 2f6c 616e 672f met..Ljava/lang/ 0000030: 5374 7269 6e67 3b78 7074 0003 626f 62 String;xpt..bob |
Il explique que cette vulnérabilité avait déjà été présentée le 28 janvier de cette année à l’AppSecCali par Gabriel Lawrence et Chris Frohoff. Il y a neuf mois, pendant leur speech, Gabriel et Chris se sont servis d’une vulnérabilité lors de la désérialisation dans la bibliothèque Common Collections, « une bibliothèque qui s’avère EXTRÊMEMENT populaire dans le monde Java », précise Breen.
« Ce que cela signifie c’est que toute application ou framework d’application qui l’utilise (et il y en a beaucoup) et désérialise des données non fiables (il y en a aussi beaucoup) est désormais sujet d’une vulnérabilité CVSS 10.0 ». Cela implique que toute personne sur votre réseau et potentiellement sur internet peut compromettre plusieurs de vos applications serveur, mais également que cette vulnérabilité s’exécute dans la mémoire.
« Chaque application serveur vient avec son propre ensemble de bibliothèques. Pire encore, chaque application que vous déployez sur le serveur vient souvent avec son propre ensemble également », a déclaré Breen. « Pour résoudre complètement ce problème, vous avez besoin de trouver et de mettre à jour chacune de ces bibliothèques individuellement ».
Se basant sur la façon dont Java exécute le code défini par l'utilisateur lors de la désérialisation des objets, Breen a expliqué qu’il était possible de personnaliser des charges utiles pour obtenir un accès shell sur les machines où sont exécutés WebLogic, WebSphere, JBoss, Jenkins, OpenNMS, et toute machine qui utilise Java Remote Method Invocation.
Quand il rédigeait son billet, Breen a déclaré que tous les produits mentionnés étaient vulnérables. Il faut noter qu’entretemps Apache et Jenkins sont en train de travailler sur un patch à apporter à leur code. Jenkins a proposé une solution de contournement qui désactive le système Jenkins CLI utilisé pour l’attaque, un correctif devrait être publié mercredi.
« Malheureusement, la vulnérabilité ne nous a pas été révélée avant sa publication, alors nous travaillons toujours sur un correctif plus efficace », a expliqué l'équipe Jenkins. Même son de cloche chez OpenNMS dont le PDG Jeff Gehlbach a avancé sur Twitter que Breen aurait dû notifier les projets affectés par la vulnérabilité zéro-day avant la publication du billet. En retour, Breen a avancé qu’il ne la considérait pas comme étant une vulnérabilité zéro-day.
De son côté, Apache Commons a proposé un correctif dans la branche 3.2.X qui apporte un drapeau pour désactiver la sérialisation sur la classe vulnérable InvokerTransformer par défaut. « Utiliser la nouvelle version de la bibliothèque signifie que toute tentative de désérialiser une InvokerTransformer va entraîner une exception », a expliqué le développeur Thomas Neidhart, de l’équipe Apache Commons.
Source : blog FoxGlove Security, Jenkins, twitter JeffGehlbach, patch InvokerTransformer