Análisis de Registros DNS para Exfiltración
Cuándo Usarlo
Usa esta habilidad cuando:
- El equipo del SOC sospeche exfiltración de datos a través de túneles DNS para eludir firewalls/proxies
- La inteligencia de amenazas indique que los adversarios usan canales C2 basados en DNS (p. ej., Cobalt Strike DNS beacon)
- UEBA detecte volúmenes anómalos de consultas DNS desde hosts específicos
- El análisis de malware revele capacidades de DNS-over-HTTPS (DoH) o túneles DNS
No lo uses para la resolución de problemas estándar de DNS o monitoreo de disponibilidad: esta habilidad se centra en la detección de abuso de DNS relacionado con la seguridad.
Requisitos Previos
- Registro de consultas DNS habilitado (Windows DNS Server, Bind, Infoblox o Cisco Umbrella)
- Registros DNS ingeridos en SIEM (Splunk con
Stream:DNS,dnssourcetype, o registros DNS de Zeek) - Datos DNS pasivos para el análisis histórico de resolución de dominios
- Línea base del comportamiento normal de DNS (volumen de consultas, distribución de dominios, frecuencia de registros TXT)
- Python con las librerías
mathycollectionspara el cálculo de entropía
Flujo de Trabajo
Paso 1: Detección de Túneles DNS Mediante Análisis de Longitud de Subdominio
Los túneles DNS codifican datos en etiquetas de subdominio, creando consultas inusualmente largas:
index=dns sourcetype="stream:dns" query_type IN ("A", "AAAA", "TXT", "CNAME", "MX")
| eval domain_parts = split(query, ".")
| eval subdomain = mvindex(domain_parts, 0, mvcount(domain_parts)-3)
| eval subdomain_str = mvjoin(subdomain, ".")
| eval subdomain_len = len(subdomain_str)
| eval tld = mvindex(domain_parts, -1)
| eval registered_domain = mvindex(domain_parts, -2).".".tld
| where subdomain_len > 50
| stats count AS queries, dc(query) AS unique_queries,
avg(subdomain_len) AS avg_subdomain_len,
max(subdomain_len) AS max_subdomain_len,
values(src_ip) AS sources
by registered_domain
| where queries > 20
| sort - avg_subdomain_len
| table registered_domain, queries, unique_queries, avg_subdomain_len, max_subdomain_len, sources
Paso 2: Detección de Consultas de Dominio de Alta Entropía (Detección de DGA)
Los algoritmos de generación de dominios producen nombres de dominio aparentemente aleatorios:
index=dns sourcetype="stream:dns"
| eval domain_parts = split(query, ".")
| eval sld = mvindex(domain_parts, -2)
| eval sld_len = len(sld)
| eval char_count = sld_len
| eval vowels = len(replace(sld, "[^aeiou]", ""))
| eval consonants = len(replace(sld, "[^bcdfghjklmnpqrstvwxyz]", ""))
| eval digits = len(replace(sld, "[^0-9]", ""))
| eval vowel_ratio = if(char_count > 0, vowels / char_count, 0)
| eval digit_ratio = if(char_count > 0, digits / char_count, 0)
| where sld_len > 12 AND (vowel_ratio < 0.2 OR digit_ratio > 0.3)
| stats count AS queries, dc(query) AS unique_domains, values(src_ip) AS sources
by query
| where unique_domains > 10
| sort - queries
Cálculo de entropía de Shannon basado en Python para consultas DNS:
import math
from collections import Counter
def shannon_entropy(text):
"""Calculate Shannon entropy of a string"""
if not text:
return 0
counter = Counter(text.lower())
length = len(text)
entropy = -sum(
(count / length) * math.log2(count / length)
for count in counter.values()
)
return round(entropy, 4)
# Test with examples
normal_domain = "google" # Low entropy
dga_domain = "x8kj2m9p4qw7n" # High entropy
tunnel_subdomain = "aGVsbG8gd29ybGQ.evil.com" # Base64 encoded data
print(f"Normal: {shannon_entropy(normal_domain)}") # ~2.25
print(f"DGA: {shannon_entropy(dga_domain)}") # ~3.70
print(f"Tunnel: {shannon_entropy(tunnel_subdomain)}") # ~3.50
# Threshold: entropy > 3.5 for subdomain = likely tunneling/DGA
Implementación de puntuación de entropía en Splunk:
index=dns sourcetype="stream:dns"
| eval domain_parts = split(query, ".")
| eval check_string = mvindex(domain_parts, 0)
| eval check_len = len(check_string)
| where check_len > 8
| eval chars = split(check_string, "")
| stats count AS total_chars, dc(chars) AS unique_chars by query, src_ip, check_string, check_len
| eval entropy_estimate = log(unique_chars, 2) * (unique_chars / check_len)
| where entropy_estimate > 3.5
| stats count AS high_entropy_queries, dc(query) AS unique_queries by src_ip
| where high_entropy_queries > 50
| sort - high_entropy_queries
Paso 3: Detección de Volumen Anómalo de Consultas DNS
Identifica hosts que generan tráfico DNS anormal:
index=dns sourcetype="stream:dns" earliest=-24h
| bin _time span=1h
| stats count AS queries, dc(query) AS unique_domains by src_ip, _time
| eventstats avg(queries) AS avg_queries, stdev(queries) AS stdev_queries by src_ip
| eval z_score = (queries - avg_queries) / stdev_queries
| where z_score > 3 OR queries > 5000
| sort - z_score
| table _time, src_ip, queries, unique_domains, avg_queries, z_score
Detección de abuso de registros TXT (método de túnel común):
index=dns sourcetype="stream:dns" query_type="TXT"
| stats count AS txt_queries, dc(query) AS unique_txt_domains,
values(query) AS domains by src_ip
| where txt_queries > 100
| eval suspicion = case(
txt_queries > 1000, "CRÍTICO — Posible túnel DNS",
txt_queries > 500, "ALTO — Posible túnel DNS",
txt_queries > 100, "MEDIO — Volumen TXT inusual"
)
| sort - txt_queries
| table src_ip, txt_queries, unique_txt_domains, suspicion
Paso 4: Detección de Herramientas de Túneles DNS Conocidas
Busca firmas de herramientas comunes de túneles DNS:
index=dns sourcetype="stream:dns"
| eval query_lower = lower(query)
| where (
match(query_lower, "\.dnscat\.") OR
match(query_lower, "\.dns2tcp\.") OR
match(query_lower, "\.iodine\.") OR
match(query_lower, "\.dnscapy\.") OR
match(query_lower, "\.cobalt.*\.beacon") OR
query_type="NULL" OR
(query_type="TXT" AND len(query) > 100)
)
| stats count by src_ip, query, query_type
| sort - count
Detección de DNS sobre HTTPS (DoH) eludiendo el DNS local:
index=proxy OR index=firewall
dest IN ("1.1.1.1", "1.0.0.1", "8.8.8.8", "8.8.4.4",
"9.9.9.9", "149.112.112.112", "208.67.222.222")
dest_port=443
| stats sum(bytes_out) AS total_bytes, count AS connections by src_ip, dest
| where connections > 100 OR total_bytes > 10485760
| eval alert = "Posible bypass de DoH — consultas DNS enviadas por HTTPS a resolver público"
| sort - total_bytes
Paso 5: Correlacionar Hallazgos DNS con Datos de Endpoint
Cruza referencias DNS sospechosas con datos de procesos:
index=dns src_ip="192.168.1.105" query="*.evil-tunnel.com" earliest=-24h
| stats count AS dns_queries, earliest(_time) AS first_query, latest(_time) AS last_query
by src_ip, query
| join src_ip [
search index=sysmon EventCode=3 DestinationPort=53 Computer="WORKSTATION-042"
| stats count AS connections, values(Image) AS processes by SourceIp
| rename SourceIp AS src_ip
]
| table src_ip, query, dns_queries, first_query, last_query, processes
Paso 6: Calcular la Estimación del Volumen de Datos Exfiltrados
Estima el volumen de datos codificados en las consultas DNS:
index=dns src_ip="192.168.1.105" query="*.evil-tunnel.com" earliest=-24h
| eval domain_parts = split(query, ".")
| eval encoded_data = mvindex(domain_parts, 0)
| eval encoded_bytes = len(encoded_data)
| eval decoded_bytes = encoded_bytes * 0.75 -- Factor de decodificación Base64
| stats sum(decoded_bytes) AS total_bytes_estimated, count AS total_queries,
earliest(_time) AS first_seen, latest(_time) AS last_seen
| eval estimated_kb = round(total_bytes_estimated / 1024, 1)
| eval estimated_mb = round(total_bytes_estimated / 1048576, 2)
| eval duration_hours = round((last_seen - first_seen) / 3600, 1)
| eval rate_kbps = round(estimated_kb / (duration_hours * 3600) * 8, 2)
| table total_queries, estimated_mb, duration_hours, rate_kbps, first_seen, last_seen
Conceptos Clave
| Término | Definición |
|---|---|
| Túnel DNS | Técnica que codifica datos dentro de consultas/respuestas DNS para exfiltrar datos o establecer canales C2 a través de DNS |
| DGA | Algoritmo de Generación de Dominios: técnica de malware que genera nombres de dominio pseudoaleatorios para la resiliencia de C2 |
| Entropía de Shannon | Medida matemática de aleatoriedad en una cadena: alta entropía (>3.5) en nombres de dominio indica DGA o túnel |
| Abuso de Registros TXT | Uso de registros TXT de DNS (diseñados para datos de texto) como canal de alto ancho de banda para túneles de datos |
| DNS sobre HTTPS (DoH) | Consultas DNS cifradas a través de HTTPS (puerto 443), que eluden la monitorización tradicional de DNS |
| DNS Pasivo | Registro histórico de resoluciones DNS que muestra a qué IPs se resolvió un dominio a lo largo del tiempo |
Herramientas y Sistemas
- Splunk Stream: Complemento de captura de tráfico de red que proporciona datos analizados de consultas DNS para análisis en SIEM
- Zeek (Bro): Monitor de seguridad de red que genera registros detallados de transacciones DNS para su análisis
- Cisco Umbrella (OpenDNS): Plataforma de seguridad DNS en la nube que bloquea dominios maliciosos y registra datos de consultas
- Infoblox DNS Firewall: Seguridad a nivel DNS que proporciona bloqueo basado en RPZ y registro detallado de consultas
- Farsight DNSDB: Base de datos DNS pasiva para búsquedas históricas de resolución de dominios y mapeo de infraestructura
Escenarios Comunes
- Cobalt Strike DNS Beacon: Detecta consultas TXT periódicas con cargas útiles codificadas hacia un dominio C2
- Exfiltración de Datos: Grandes volúmenes de consultas de subdominios únicos que codifican datos robados en Base64/hexadecimal
- Malware DGA: Detecta consultas DNS a dominios generados algorítmicamente (alta entropía, sin contenido web)
- ByPass de DNS-over-HTTPS: Empleado que usa DoH para eludir el filtrado y monitoreo de DNS corporativo
- Exfiltración Lenta y Continua: Túnel DNS de bajo volumen que se mantiene por debajo de los umbrales de alerta (requiere comparación con la línea base)
Formato de Salida
ANÁLISIS DE EXFILTRACIÓN DNS — WORKSTATION-042
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Período: 2024-03-14 a 2024-03-15
Origen: 192.168.1.105 (WORKSTATION-042, Depto. Finanzas)
Hallazgos:
[CRÍTICO] Túnel DNS detectado hacia evil-tunnel[.]com
Volumen de Consultas: 12,847 consultas en 18 horas
Longitud Promedio de Subdominio: 63 caracteres (normal: <20)
Entropía Promedio: 3.82 (umbral: 3.5)
Tipos de Consulta: TXT (89%), A (11%)
Datos Estimados: ~4.7 MB exfiltrados vía DNS
Tasa: 0.58 kbps (patrón de goteo lento)
[ALTO] Dominios similares a DGA resueltos
Dominios DGA Únicos: 247 dominios resueltos
Patrón: 15 caracteres alfanuméricos aleatorios .xyz TLD
Rango de Entropía: 3.6 - 4.1
Atribución de Proceso:
Proceso: svchost_update.exe (suplantación — no es svchost legítimo)
PID: 4892
Padre: explorer.exe
Hash: SHA256: a1b2c3d4... (VT: 34/72 malicioso — Cobalt Strike beacon)
Contención:
[HECHO] Host aislado vía EDR
[HECHO] Dominio evil-tunnel[.]com agregado a sumidero DNS
[HECHO] Incidente IR-2024-0448 creado