Le problème fondamental de la sérialisation est que les zones qui peuvent être attaquées sont trop grandes et se propagent constamment. Le graphe d'objets (une image d'un groupe d'objets connectés par référence) est restauré par la méthode readObject d'ObjectInputStream. Cette méthode vous permet d'instancier des objets de presque n'importe quel type sur le chemin de classe tant que le type implémente Serializable. Dans le processus de désérialisation d'un flux d'octets, cette méthode peut exécuter n'importe quel type de code qui se trouve sur le chemin de classe et implémente Serialize. Par conséquent, tous ces types sont ciblés pour les attaques.
Même si vous supprimez la vulnérabilité d'une classe sérialisable qui peut être attaquée, l'application elle-même peut toujours être vulnérable. Si vous créez une chaîne de gadgets en combinant des méthodes qui peuvent effectuer des traitements dangereux (appelés gadgets) à partir de méthodes de type sérialisable, il arrive parfois que du code natif arbitraire puisse être exécuté. En fait, en 2016, un piratage a été commis contre la San Francisco Metropolitan Transit Agency Municipal Railway en utilisant ce mécanisme.
Comme mentionné ci-dessus, vous pouvez facilement créer un flux qui prend beaucoup de temps à désérialiser sans utiliser de gadget, et vous pouvez faire une attaque DoS avec cela. Ces flux sont appelés bombes de désérialisation.
// Deserialization bomb - deserializing this stream takes forever
static byte[] bomb() {
Set<Object> root = new HashSet<>();
Set<Object> s1 = root;
Set<Object> s2 = new HashSet<>();
for (int i = 0; i < 100; i++) {
Set<Object> t1 = new HashSet<>();
Set<Object> t2 = new HashSet<>();
t1.add("foo"); // Make t1 unequal to t2
s1.add(t1); s1.add(t2);
s2.add(t1); s2.add(t2);
s1 = t1;
s2 = t2;
}
return serialize(root); // Method omitted for brevity
}
Dans le code ci-dessus, un HashSet a 100 couches d'éléments HashSet. Lorsque vous créez une instance HashSet, vous devez calculer le hashcode pour chaque élément. Par conséquent, il est nécessaire d'appeler hashCode 2 à la 100e puissance et le traitement n'est pas du tout terminé.
Comment gérer les attaques ci-dessus liées à la sérialisation?
** Le meilleur moyen est de ne pas désérialiser. ** ** ** Il n'y a aucune raison d'utiliser la sérialisation Java dans les nouveaux systèmes. ** ** Il existe de meilleurs mécanismes pour traduire les séquences d'octets et les objets, appelés représentations de données structurées multiplateformes.
Les représentations typiques de données structurées multiplateformes sont JSON et Protobuf. La différence la plus importante entre JSON et Protobuf est que JSON est basé sur du texte et lisible par l'homme, tandis que Protobuf est plus efficace en binaire.
Pour la maintenance et le développement de systèmes hérités, la sérialisation en Java peut être inévitable. Dans de tels cas, vous devez prendre des mesures telles que ** ne pas désérialiser les données non fiables **. En particulier, n'acceptez pas le trafic RMI provenant de sources non fiables.
Si la sérialisation en Java est inévitable et que la sécurité des données à désérialiser est douteuse, java.io.ObjectInputFilter (introduit à partir de Java9 et rétroporté vers 6,7,8) est un filtre de désérialisation d'objet. ) Devrait être utilisé. Cela offre la possibilité de filtrer les flux avant qu'ils ne soient désérialisés. Il est décidé de rejeter ou d'accepter classe par classe. À ce stade, vous pouvez sélectionner le format de liste blanche ou le format de liste noire, mais comme la liste noire ne peut protéger que des menaces connues, la liste blanche doit être adoptée. La fonction de filtrage empêche également une utilisation excessive de la mémoire et des graphes d'objets trop profonds, mais elle n'empêche pas les bombes de désérialisation mentionnées ci-dessus.
Recommended Posts