IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Un chercheur en sécurité donne des détails sur la vulnérabilité RCE
Qui affecte la bibliothèque Java Apache Commons Collection

Le , par Stéphane le calme

81PARTAGES

5  0 
Steve Breen, un chercheur en sécurité travaillant pour le compte de Foxglove Security, s’est exprimé au sujet d’une faille dans la bibliothèque Apache Commons due à une méthode grâce à laquelle Java désérialise les objets et l’utilise pour exploiter les installations JBoss, WebSphere et WebLogic.

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();
    }
}
Ce que ce code fait c’est simplement d’écrire la chaîne de caractères « bob » dans le disque, puis le lit et affiche le résultat. Voici les résultats obtenus lorsque ce code est exécuté.

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
Il faut préciser que le fichier name.ser sur le disque est binaire, « il a quelques caractères non imprimables, en particulier l’octet ‘aced 005’ ».

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+"!";
    }
}
Voici le résultat qui s’affiche lorsqu’on l’exécute.

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
En regardant le résultat, au lieu de voir s’afficher « bob » comme défini en entrée, c’est plutôt « bob! » qui est imprimé. Pour le comprendre, « la clé ici est la méthode readObject bien entendu. Lorsque Java lit un objet sérialisé, la première chose qu’il fait après avoir lu des octets raw, c’est d’appeler la méthode définie par l’utilisateur ‘readObject’ si elle existe. Dans ce cas, la méthode ‘readObject’ ajoute un point d’exclamation au nom » explique Breen. « Et si au lieu d’ajouter un point d’exclamation à une chaîne de caractères définie par l’utilisateur, elle était utilisée pour exécuter une commande définie par l’utilisateur sur le système d’exploitation ? »

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

Une erreur dans cette actualité ? Signalez-nous-la !

Avatar de Logicielz
Futur Membre du Club https://www.developpez.com
Le 10/11/2015 à 12:30
marshalling et unmarshalling, non marshmalling et unmarshmalling
1  0 
Avatar de atha2
Membre éprouvé https://www.developpez.com
Le 10/11/2015 à 13:12
Il y a sûrement un truc qui m'échappe mais exécuter un fichier object lu dans la méthode readObject, sans valider son contenu et son origine, me semble aussi débile (plus en faite vu que normalement readObject ne sert pas à ça) que faire une requête SQL non préparée sans valider le formulaire uploadé par un utilisateur.
1  0 
Avatar de syj
Membre régulier https://www.developpez.com
Le 10/11/2015 à 19:42
Je trouve que l'article n'est pas clair. Le problème ne vient pas seulement du readObject !
Quand vous déserialisez du code, il ne charge pas "des classes" en mémoire. Il charge juste des attributs. Le code n'est pas embarqué dans la sérialisation.

Le problème vient d'une classe comme InvokerTransformer
http://svn.apache.org/repos/asf/comm...ansformer.java

A partir de cette classe, vous pouvez appeler la méthode d'une classe avec les paramètres que vous vous voulez.

Alors, si vous trouvez un objet qui a dans son readObject, la possibilité d'appeler d'une Instance de InvokerTransformer que vous avez préparré pour éxecuter la méthode que vous souhaitez avec les paramètres de votre de choix.

Mais, il y a moyen d'avoir l'éxecution de votre InvokerTransformer par d'autre moyen que le readObject. Le readObject est juste un moyen , mais ce n'est pas l'unique moyen d'exploiter une classe comme InvokerTransformer
1  0 
Avatar de OButterlin
Modérateur https://www.developpez.com
Le 12/11/2015 à 17:02
Pour sérialiser, c'est writeObject qui est utilisé, pas readObject.

Ensuite, le fichier .ser contient la valeur des attributs de l'objet mais pas la classe elle même.
L'application utilisatrice garde sa définition de classe et n'est pas impactée le mécanisme de désérialisation.
Finalement, je ne vois pas trop où est le problème
1  0 
Avatar de OButterlin
Modérateur https://www.developpez.com
Le 10/11/2015 à 14:44
Citation Envoyé par atha2 Voir le message
Il y a sûrement un truc qui m'échappe mais exécuter un fichier object lu dans la méthode readObject, sans valider son contenu et son origine, me semble aussi débile (plus en faite vu que normalement readObject ne sert pas à ça) que faire une requête SQL non préparée sans valider le formulaire uploadé par un utilisateur.
Que veux-tu dire ?

La désérialisation n'a pas à se poser la question de ce que fait l'objet qu'elle va créer, comment le pourrait-elle ?
0  0 
Avatar de OButterlin
Modérateur https://www.developpez.com
Le 10/11/2015 à 14:45
Citation Envoyé par Logicielz Voir le message
marshalling et unmarshalling, non marshmalling et unmarshmalling
Stéphane est un fan de marshmallow, c'est sûr
0  0 
Avatar de heid
Membre confirmé https://www.developpez.com
Le 10/11/2015 à 18:18
J'ai été voir sur la mailing list les mec d'apache ont été au courant après moi ...

Comment tu peux sérieusement écrire un article sans prévenir les mains commiteurs quelque jours avant histoire que le fix soit dispo le jour de la publication. C'est pas sérieux comme comportement. Même si l'exploit existe depuis longtemps il devait se douter que ça allait faire du bruit.
0  0 
Avatar de Gugelhupf
Modérateur https://www.developpez.com
Le 11/11/2015 à 11:09
Bonjour @syj,
Le protocole de sérialisation contient les métadonnées (les types des attributs) ainsi que des valeurs.
Je n'ai pas cherché en détail la source du problème mais le 2ème exemple montre que le problème vient forcément de "Java", donc :
  • Soit du protocole de sérialisation (writeObject)
  • Soit du protocole de désérialisation (readObject)
  • Soit du protocole lui-même (c'est-à-dire la manière dont les métadonnées et valeurs sont structurés)

InvokerTransformer n'est au final qu'un wrapper qui utilise des classes de base.

EDIT 13/11/2015: Lorsque j'ai dit que le "problème" venait forcément de Java en citant le 2ème exemple comme référence, c'est que ce dernier ne contient que tu code Java, pas Apache, j'aurais du chercher en détail la source du problème avant de répondre mais comme moi voisin ci-dessous l'a dit : le "problème" évoqué est lié au fait qu'on puisse redéfinir une méthode magique readObject de la classe qu'on désérialise. C'est une notion de la sérialisation Java qu'on utilise peu mais qui existe.
0  0 
Avatar de thelvin
Modérateur https://www.developpez.com
Le 12/11/2015 à 11:04
Disons que le problème vient du fait qu'il soit possible d'ajouter un comportement lors d'une désérialisation : la désérialisation est pensée comme ne faisant que lire des données et les mettre dans un objet en mémoire. Pas faire des choses annexes.

Mais, dans un langage objet, c'est censé aller de soi, tout comme on a besoin d'un constructeur pour s'assurer de mettre l'objet dans un état initialisé cohérent avant de pouvoir s'en servir.
Du coup, ce n'est pas tellement évitable tout en restant dans le principe objet.

La raison pour laquelle ça pose problème dans la désérialisation mais pas dans un constructeur, c'est qu'on ne sait pas ce qu'il y a dans les données qu'on s'apprête à désérialiser. Cela peut venir de n'importe où et contenir n'importe quoi. Et ça, c'est vraiment pas OK du tout, tant en termes de sécurité que de stabilité.
Le programmeur sait quels objets il va chercher à utiliser une fois que la désérialisation sera faite. Il ne devrait pas avoir à se méfier de ce qui se passe si ce ne sont en fait pas ces objets-là. Ça devrait être "quelque chose comme un ClassCastException" et puis c'est tout. Il faudrait que la désérialisation sache quels objets elle est censée fournir, et fasse une erreur si elle tombe sur autre chose.
Sur le principe ce "ça devrait" est bien joli, mais ce n'est pas évident à définir dès que les objets sont des conteneurs génériques d'autres objets. Quoi qu'il en soit, pour la sérialisation Java c'est trop tard : elle ne le fait pas et c'est tout. La seule vraie solution serait de ne pas l'utiliser.

Dans la même vaine, on peut s'inspirer de ce qui se passe quand on fait de la sérialisation en Java, mais pas avec la sérialisation native Java. XML, JSON, Protocol Buffers ou autres trucs maison. Ceux-là désérialisent bel et bien en sachant a priori quel objet doit être produit. Si le programmeur s'amuse à désérialiser des objets qui ont un comportement et qui viennent d'une ressource peu fiable, on n'y peut plus grand-chose.
0  0 
Avatar de syj
Membre régulier https://www.developpez.com
Le 12/11/2015 à 15:19
@Gugelhupf
Tu est surrement d'accord avec ce cas ?

Le pirate sérialise un objet avec un attribut name="toto" dans objet.ser avec la classe:

Code : Sélectionner tout
1
2
3
4
5
6
7
class MyObject implements Serializable{
    public String name;
    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
        in.defaultReadObject();
        this.name = this.name + "!";
    }
}
L'utilisateur récupère "objet.ser", puis il le déserialise avec le code suivant:
Code : Sélectionner tout
1
2
3
4
5
6
7
class MyObject implements Serializable{
    public String name;
    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
        in.defaultReadObject();
        this.name = this.name;
    }
}
L'utilisateur aura dans son attribut name="toto" et non "toto!".

C'est pourquoi , un meilleur exemple aurait été un code de ce type:

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
class MyObject implements Serializable{
    public Transformer init;
    public Object o;
	
    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
        in.defaultReadObject();
	init.transform(o);
    }
}

Le pirate crée objet.ser avec un MyObject avec :
- o = new File("un fichier a ne pas supprimé"
- init = InvokerTransformer.getInstance("delete";

Lorsque l'utilisateur désarialisera le objet.ser du pirate, il éxecutera la commande que le pirate a préparré.

Après , un objet de ce type serait aussi problèmatique si onEvent est systématiquement appellé par le programme chargeant l'objet.ser.
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Plugin implements Serializable{
    public Transformer init;
    public Object o;
	
    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
        in.defaultReadObject();	
    }
 
    public onEvent() {
	init.transform(o);
    } 

}
0  0