Analisando Logs DNS para Exfiltração
Quando Usar
Use esta skill quando:
- Equipes de SOC suspeitam de exfiltração de dados via tunelamento DNS para contornar firewalls/proxies
- Inteligência de ameaças indica adversários usando canais C2 baseados em DNS (ex.: Cobalt Strike DNS beacon)
- UEBA detecta volumes anômalos de consultas DNS em hosts específicos
- Análise de malware revela capacidades de DNS-over-HTTPS (DoH) ou tunelamento DNS
Não use para solução de problemas padrão de DNS ou monitoramento de disponibilidade — esta skill foca na detecção de abuso de DNS relacionado à segurança.
Pré-requisitos
- Logging de consultas DNS habilitado (Windows DNS Server, Bind, Infoblox ou Cisco Umbrella)
- Logs DNS ingeridos no SIEM (Splunk com
Stream:DNS, sourcetypedns, ou logs Zeek DNS) - Dados DNS passivos para análise histórica de resolução de domínios
- Linha de base do comportamento normal de DNS (volume de consultas, distribuição de domínios, frequência de registros TXT)
- Python com bibliotecas
mathecollectionspara cálculo de entropia
Fluxo de Trabalho
Passo 1: Detecção de Tunelamento DNS via Análise de Comprimento de Subdomínio
O tunelamento DNS codifica dados em rótulos de subdomínio, criando consultas excepcionalmente longas:
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
Passo 2: Detecção de Consultas a Domínios de Alta Entropia (Detecção de DGA)
Algoritmos de Geração de Domínios produzem domínios de aparência aleatória:
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 Entropia de Shannon em Python para consultas DNS:
import math
from collections import Counter
def shannon_entropy(text):
"""Calcula a entropia de Shannon de uma 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)
# Teste com exemplos
normal_domain = "google" # Baixa entropia
dga_domain = "x8kj2m9p4qw7n" # Alta entropia
tunnel_subdomain = "aGVsbG8gd29ybGQ.evil.com" # Dados codificados em Base64
print(f"Normal: {shannon_entropy(normal_domain)}") # ~2.25
print(f"DGA: {shannon_entropy(dga_domain)}") # ~3.70
print(f"Túnel: {shannon_entropy(tunnel_subdomain)}") # ~3.50
# Limiar: entropia > 3,5 para subdomínio = provável tunelamento/DGA
Implementação em Splunk da pontuação de entropia:
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
Passo 3: Detecção de Volume Anômalo de Consultas DNS
Identifique hosts gerando tráfego 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
Detecção de abuso de registro TXT (método comum de tunelamento):
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 — Provável tunelamento DNS",
txt_queries > 500, "ALTO — Possível tunelamento DNS",
txt_queries > 100, "MÉDIO — Volume TXT incomum"
)
| sort - txt_queries
| table src_ip, txt_queries, unique_txt_domains, suspicion
Passo 4: Detecção de Ferramentas Conhecidas de Tunelamento DNS
Pesquise assinaturas de ferramentas comuns de tunelamento 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
Detecção de DNS over HTTPS (DoH) contornando 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 = "Possível bypass DoH — consultas DNS enviadas por HTTPS para resolvedor público"
| sort - total_bytes
Passo 5: Correlacionar Achados DNS com Dados de Endpoint
Cruzamento de DNS suspeito com dados de processo:
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
Passo 6: Calcular Estimativa de Volume de Dados Exfiltrados
Estime o volume de dados codificados em 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 -- Fator de decodificação 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
Conceitos-Chave
| Termo | Definição |
|---|---|
| Tunelamento DNS | Técnica que codifica dados em consultas/respostas DNS para exfiltrar dados ou estabelecer canais C2 através do DNS |
| DGA | Algoritmo de Geração de Domínios — técnica de malware que gera nomes de domínio pseudoaleatórios para resiliência de C2 |
| Entropia de Shannon | Medida matemática da aleatoriedade de uma string — alta entropia (>3,5) em nomes de domínio indica DGA ou tunelamento |
| Abuso de Registro TXT | Uso de registros DNS TXT (projetados para dados de texto) como canal de alta largura de banda para tunelamento de dados |
| DNS over HTTPS (DoH) | Consultas DNS criptografadas por HTTPS (porta 443), contornando o monitoramento DNS tradicional |
| DNS Passivo | Registro histórico de resoluções DNS, mostrando para quais IPs um domínio resolveu ao longo do tempo |
Ferramentas e Sistemas
- Splunk Stream: Add-on de captura de tráfego de rede que fornece dados de consultas DNS analisados para análise no SIEM
- Zeek (Bro): Monitor de segurança de rede que gera logs detalhados de transações DNS para análise
- Cisco Umbrella (OpenDNS): Plataforma de segurança DNS em nuvem que bloqueia domínios maliciosos e registra dados de consulta
- Infoblox DNS Firewall: Segurança em camada DNS que fornece bloqueio baseado em RPZ e logging detalhado de consultas
- Farsight DNSDB: Banco de dados DNS passivo para consultas históricas de resolução de domínios e mapeamento de infraestrutura
Cenários Comuns
- Cobalt Strike DNS Beacon: Detecta consultas TXT periódicas com payloads codificados para domínio C2
- Exfiltração de Dados: Grandes volumes de consultas a subdomínios exclusivos codificando dados roubados em Base64/hex
- Malware DGA: Detecta consultas DNS para domínios gerados algoritmicamente (alta entropia, sem conteúdo web)
- Bypass DNS-over-HTTPS: Funcionário usando DoH para contornar filtragem e monitoramento DNS corporativo
- Exfiltração Gota a Gota: Tunelamento DNS de baixo volume que permanece abaixo dos limites de alerta (requer comparação com linha de base)
Formato de Saída
ANÁLISE DE EXFILTRAÇÃO DNS — WORKSTATION-042
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Período: 2024-03-14 a 2024-03-15
Origem: 192.168.1.105 (WORKSTATION-042, Depto Financeiro)
Achados:
[CRÍTICO] Tunelamento DNS detectado para evil-tunnel[.]com
Volume de Consultas: 12.847 consultas em 18 horas
Comp. Médio Subdomínio: 63 caracteres (normal: <20)
Entropia Média: 3,82 (limiar: 3,5)
Tipos de Consulta: TXT (89%), A (11%)
Dados Estimados: ~4,7 MB exfiltrados via DNS
Taxa: 0,58 kbps (padrão gota a gota)
[ALTO] Domínios semelhantes a DGA resolvidos
Domínios DGA Únicos: 247 domínios resolvidos
Padrão: 15 caracteres alfanumérico aleatório .xyz TLD
Faixa de Entropia: 3,6 - 4,1
Atribuição de Processo:
Processo: svchost_update.exe (mascarado — não é svchost legítimo)
PID: 4892
Pai: explorer.exe
Hash: SHA256: a1b2c3d4... (VT: 34/72 malicioso — beacon Cobalt Strike)
Contenção:
[CONCLUÍDO] Host isolado via EDR
[CONCLUÍDO] Domínio evil-tunnel[.]com adicionado ao sinkhole DNS
[CONCLUÍDO] Incidente IR-2024-0448 criado