Análisis en profundidad

Cómo funciona el exploit de la vulnerabilidad de Log4j

  • Home
  • Ciber Seguridad Informatica

Log4Shell es la “vulnerabilidad más crítica de la última década” que ha surgido en Log4j, el popular marco de registro utilizado por numerosos productos de software, servicios en la nube y otras aplicaciones. Cómo funciona su exploit y consejos para asegurar las aplicaciones empresariales desarrolladas internamente

Publicado el 31 Ene 2023

Log4j

La vulnerabilidad surgida en Log4j, la utilidad de registro de Apache escrita en lenguaje Java (CVE-2021-44228), ha sido descrita como “la vulnerabilidad más crítica de la última década”. También conocida como Log4Shell, esta criticidad ha obligado a los desarrolladores de numerosos productos de software a lanzar actualizaciones o mitigaciones para sus clientes. Y desde que se descubrió la vulnerabilidad, los responsables  de Log4j han publicado dos nuevas versiones – la segunda de las cuales eliminó por completo la función que originalmente había hecho posible el exploit.

Como ya se ha señalado, Log4Shell es un exploit relacionado con la función de ‘sustitución de mensajes’ de Log4j, que permite modificar el registro de eventos mediante programación insertando cadenas formateadas para recuperar contenido externo. El código detrás de esta función también permitía realizar búsquedas o ‘look-ups’ utilizando URLs JNDI (Java Naming and Directory Interface).

Sin embargo, esta función ofrecía inadvertidamente a un atacante la posibilidad de insertar texto que contuviera URLs JNDI maliciosas dentro de las peticiones enviadas al software que utilizaba Log4j, dando lugar a que el código remoto fuera cargado y ejecutado por el registrador. Para comprender mejor lo peligrosos que son los exploits de esta función, analizaremos a continuación el código que los hace posibles.

Cómo funciona Log4j

Log4j produce eventos de registro utilizando TTCCLayout: información de tiempo, hilo, categoría y contexto. Por defecto, utiliza el siguiente patrón:

%r [%t] %-5p %c %x – %m%n

En este caso, %r imprime el tiempo en milisegundos transcurrido desde que se inició el programa; %t indica el hilo, %p la prioridad del evento, %c la categoría, %x el contexto de diagnóstico asociado al hilo que generó el evento y %m se reserva para el mensaje asociado al evento, proporcionado por la aplicación.

Es en este último campo donde entra en juego la vulnerabilidad.

La vulnerabilidad puede ser explotada cuando se llama a la función logger.error() pasando como parámetro un mensaje que incluya una URL JNDI. En el momento en que se pasa la URL, el software realiza una “búsqueda” JNDI que puede provocar la ejecución remota de código.

Para reproducir esta vulnerabilidad, podemos echar un vistazo a una de las muchas PoC (Prueba de Concepto) publicadas, que demuestra cómo muchas aplicaciones interactúan con Log4j. En el código logger/src/main/java/logger/App.java utilizado en esta PoC, observamos cómo se llama a logger.error() con un parámetro de tipo de mensaje:

registradores de paquetes;

importar org.apache.logging.log4j.LogManager;

importar org.apache.logging.log4j.logger;

aplicación de clase pública {

  registrador de registrador final estático privado = LogManager.getLogger (App.class);

  public static void main(String[] args) {

       String msg = (args.length > 0 ? args [0] : “”);

       registrador.error(mensaje);

  }

}

A efectos de depuración, cambiamos el mensaje por una URL de prueba (creada con la herramienta Interactsh) utilizando DNS con JNDI para pasarla como parámetro a la función logger.error(), siguiendo así paso a paso la ejecución del programa:

Log4j
Log4j

Podemos ver cómo, tras llamar al método logger.error() de la clase AbstractLogger con la URL especialmente creada, se llama a otro método, logMessage:

https://noticias.sophos.com/wp-content/uploads/2021/12/2-e1639677499308.png

El método log.message crea un objeto mensaje con la URL que se le ha pasado:

https://noticias.sophos.com/wp-content/uploads/2021/12/3-e1639672381678.png

A continuación, llama a processLogEvent de la clase LoggerConfig para registrar el evento:

https://noticias.sophos.com/wp-content/uploads/2021/12/4-e1639688369509.png

La siguiente llamada es al método append de la clase AbstractOutputStreamAppender, que añade el mensaje al registro:

https://noticias.sophos.com/wp-content/uploads/2021/12/5-e1639688417510.png

Aquí es donde se produce el problema

A su vez, este método llama al método directEncodeEvent:

https://noticias.sophos.com/wp-content/uploads/2021/12/6-e1639688447404.png

Y el método directEncodeEvent llama al método getLayout().Encode, que formatea el mensaje para el registro añadiendo el parámetro que se le ha pasado – que, en este caso,no es otro que la URL que hemos creado para aprovechar la vulnerabilidad:

https://noticias.sophos.com/wp-content/uploads/2021/12/7-e1639688473890.png

A continuación se crea un nuevo objeto StringBuilder:

https://noticias.sophos.com/wp-content/uploads/2021/12/8-e1639675373458.png

StringBuilder llama al método format de la clase MessagePatternConvert y analiza la URL suministrada buscando los caracteres ‘$’ y ‘{‘ para identificar la URL real:

https://noticias.sophos.com/wp-content/uploads/2021/12/9-e1639688540208.png

A continuación, intenta identificar varios nombres y valores separados por ‘:’ o ‘-‘:

https://noticias.sophos.com/wp-content/uploads/2021/12/10-e1639675483330.png

La siguiente llamada al método resolveVariable de la clase StrSubstitutor identifica las variables, que pueden ser una o varias de las siguientes:

{date, java, marker, ctx, lower, upper, jndi, main, jvmrunargs, sys, env, log4j}

En este punto, el código llama al método lookup de la clase Interpolator para comprobar el servicio asociado a la variable (que en este caso es jndi):

https://news.sophos.com/wp-content/uploads/2021/12/11-e1639675821351.png

Una vez encontrado jndi, el código llama al método lookup de la clase jndiManager, que evalúa lo que contiene el recurso jndi:

https://news.sophos.com/wp-content/uploads/2021/12/12-e1639676035606.png

A continuación, se llama al método getURLOrDefaultInitCtx de la clase IntialContext. Aquí se crea la solicitud, que luego se enviará a la interfaz JNDI para recuperar la información de contexto, en función de la URL que se haya pasado. En este punto exacto, la hazaña comienza a tomar forma. En nuestro caso, la URL se refiere al servicio DNS:

https://news.sophos.com/wp-content/uploads/2021/12/13-e1639689038571.png

Al especificar dicha URL, podemos ver con Wireshark que se envía una consulta DNS a la URL que hemos proporcionado (se trata de una URL de prueba, no peligrosa):

https://news.sophos.com/wp-content/uploads/2021/12/14.png

Si la URL es jndi:ldap://, se llama a otro método de la clase ldapURLConext para comprobar si la URL tiene queryComponents:

https://news.sophos.com/wp-content/uploads/2021/12/15-e1639689270961.png

Tras llamar al método lookup de la clase ldapURLContext, el nombre de la variable contiene la URL ldap:

https://news.sophos.com/wp-content/uploads/2021/12/16.png

Para conectarse a continuación a la URL ldap suministrada:

https://news.sophos.com/wp-content/uploads/2021/12/18.png

Se llama al método flushBuffer de la clase OutputStreamManager, aquí buf contiene los datos devueltos por el servidor LDAP, en este caso la cadena mmm…. que podemos ver a continuación:

https://news.sophos.com/wp-content/uploads/2021/12/19-e1639682998228.png

Observando los paquetes capturados con Wireshark, podemos ver que la solicitud consta de los siguientes bytes:

https://news.sophos.com/wp-content/uploads/2021/12/l4j-20.png

Estos son los datos serializados que mostrará el cliente, como podemos ver a continuación donde se ha aprovechado la vulnerabilidad: observe la cadena [main] ERROR logger.App dentro del mensaje seguido de los datos:

https://news.sophos.com/wp-content/uploads/2021/12/l4j-21.png

Problema resuelto

Esto fue posible porque en todas las versiones de Log4j 2 hasta la 2.14 (excluida la versión de seguridad 2.12.2), el soporte JNDI no estaba limitado en cuanto a los nombres que podían resolverse. Algunos protocolos no eran seguros o permitían ejecutar código remoto. Log4j 2.15.0 restringe JNDI únicamente a las búsquedas LDAP, y dichas búsquedas se restringen además por defecto a la conexión con objetos primitivos Java que residan en el host local.

Sin embargo, la versión 2.15.0 dejó la vulnerabilidad parcialmente sin resolver, ya que para las implementaciones con “ciertos patrones de diseño no predeterminados” para Log4j, como las que tienen búsquedas de contexto (como ‘$${ctx:loginId}’) o con un patrón de mapa de contexto de hilo (‘%X’, ‘%mdc’ o ‘%MDC’), todavía era posible definir datos de entrada a través de un patrón de búsqueda JNDI que podría provocar un ataque de denegación de servicio (DoS).

En las últimas versiones, todas las búsquedas se han desactivado por defecto. Así, la función JNDI se ha eliminado por completo, pero esto impide que Log4j se utilice para exploits remotos.

En conclusión

Log4j es un popular marco de registro utilizado por numerosos productos de software, servicios en la nube y otras aplicaciones.

Las vulnerabilidades de las versiones anteriores a la 2.15.0 permiten a un atacante recuperar datos de una aplicación o de su sistema operativo subyacente, en lugar de ejecutar código Java que se ejecuta con el mismo nivel de permisos que el propio tiempo de ejecución de Java (Java.exe en sistemas Windows).

Este código puede ejecutar comandos y scripts en el sistema operativo local, descargando así código peligroso adicional y allanando el camino para la elevación de privilegios y el acceso remoto persistente.

Aunque la versión 2.15.0 de Log4j, lanzada en el momento en que la vulnerabilidad se hizo de dominio público, resuelve estos problemas, deja, sin embargo, la puerta abierta a exploits y ataques de denegación de servicio (una situación que se resolvió al menos parcialmente con la versión 2.16.0).

El 18 de diciembre se publicó una tercera versión, la 2.17.0, que evita posibles ataques recursivos que podrían provocar una denegación de servicio.

Las empresas deben comprobar las versiones de Log4j en sus aplicaciones desarrolladas internamente y actualizarlas a las últimas versiones (2.12.2 para Java 7 y 2.17.0 para Java 8), así como aplicar los parches de software en cuanto los publiquen los respectivos proveedores.

Por Sean Gallagher.

Prohibida su reproducción total o parcial.

¿Qué te ha parecido este artículo?

¡Su opinión es importante para nosotros!

Temas principales

Especificaciones

A
Amenazas
A
aplicaciones
S
seguridad