Home Comunidade Eventos YouTube Networks Desafios // produtos Academy News CTI | CVE Contato

12 - Hunting de Rede com Zeek e Suricata

#fields
video explicativo
Video explicativoEspaco reservado para o video desta anotação — adicione video: https://youtu.be/... no frontmatter da nota.

Hunting de Rede com Zeek e Suricata

Nota 12 do capítulo 08 (Técnicas de Hunting por TTPs), que transpõe as técnicas por tática para a fonte de rede.
Trata Zeek como gerador de logs estruturados por protocolo e Suricata como motor de assinaturas com saída EVE JSON.
Dialoga com as notas 13 e 14 deste capítulo, com a nota 04 do capítulo 02 e com os controles DAT.2 e TH.12 do capítulo 15.

1. Contexto e Posicionamento na Biblioteca

As notas anteriores do capítulo 08 definem o que caçar por tática e por técnica do MITRE ATT&CK; esta nota responde onde, na prática, o sinal de rede aparece. O endpoint cega em três situações recorrentes: host sem agente, malware que vive apenas em memória e tráfego que imita aplicações legítimas com bibliotecas comuns. Nesses vazios, a telemetria de rede é frequentemente a única testemunha do comportamento adversário, e por isso o bloco final do capítulo começa por ela. A nota assume que o leitor já domina a formulação de hipóteses do capítulo 07 e conhece o inventário de fontes do capítulo 02.

Zeek e Suricata são os dois sensores canônicos de rede desta biblioteca e cumprem papéis diferentes sobre o mesmo cabo. Zeek observa o tráfego, interpreta protocolos e produz logs tabulares estáveis por conexão, consulta DNS, requisição HTTP, handshake TLS e arquivo transferido; é a base de qualquer NDR (Network Detection and Response) aberto. Suricata aplica assinaturas em tempo real e registra eventos detalhados em EVE JSON, cobrindo os mesmos protocolos. São visões complementares: o alerta de Suricata diz que algo casou com uma assinatura; o log de Zeek descreve a sessão inteira que contém o alerta. O desenho mais comum nos ambientes alvo é rodar os dois no mesmo ponto de observação, por TAP ou SPAN.

2. Zeek como Fonte de Caça: Logs e Campos Essenciais

O Zeek grava logs TSV por protocolo, normalmente em /opt/zeek/logs/current, com rotação diária e cabeçalho #fields declarando as colunas de cada arquivo. Todo registro carrega uid e ts, e o uid é o elo que costura a mesma conexão entre conn, dns, http, ssl e files. A extração de colunas é feita com zeek-cut, que lê a entrada por pipe e devolve apenas os campos pedidos, em qualquer ordem. A tabela abaixo concentra os seis logs de maior retorno em caça e os campos que sustentam as hipóteses do restante da nota.

Log O que descreve Campos centrais para caça
conn Cada conexão TCP, UDP ou ICMP observada id.orig_h, id.resp_h, id.resp_p, proto, service, duration, orig_bytes, resp_bytes, conn_state, history
dns Consultas e respostas DNS query, qtype_name, rcode_name, answers, TTLs, rtt
http Requisições e respostas HTTP method, host, uri, user_agent, status_code, request_body_len, response_body_len
ssl Handshakes TLS server_name, version, cipher, validation_status, subject, issuer, ja3, ja3s
files Arquivos vistos sobre qualquer transporte source, mime_type, filename, md5, sha256, seen_bytes, total_bytes
weird Anomalias de protocolo que não viraram evento estruturado name, source, addl

Dois campos do conn merecem leitura treinada porque resumem a conexão em poucos caracteres. O conn_state codifica o desfecho do handshake e do encerramento: SF indica conexão normal com encerramento limpo, S1 indica conexão estabelecida ainda aberta no fim da observação, REJ indica recusa por RST do destino e S0 indica tentativa sem qualquer resposta. O history guarda a sequência de eventos da conexão letra a letra, e padrões como origem que nunca envia dados com destino respondendo RST denunciam varredura antes de qualquer agregação estatística. Nas versões recentes do Zeek, o ssl.log já entrega ja3 e ja3s nativamente; o JA4 chega por plugins da comunidade ou pelo Suricata 7, como tratam a nota 14 deste capítulo.

3. Análise Prática por Log

A análise em Zeek é iterativa e roda bem em notebook comum: carregue o dia de logs, aplique filtros com zeek-cut, agregue com sort, uniq e awk e refine a hipótese a cada rodada. Para reproduzir um achado histórico, rode zeek -r captura.pcap local sobre um pcap retido e o Zeek recria os logs do período. O zeek-cut aceita a opção -d para converter timestamps epoch em data legível. Uma armadilha prática: campos como user_agent contêm espaços, e o awk com separador padrão desalinha colunas nesses casos; mantenha tais campos como última coluna ou processe a linha inteira com uniq.

O primeiro exemplo de conn.log lista os maiores envios de dados por conexão, filtro inicial de exfiltração que a nota 13 aprofunda em escala de fluxo.

cat conn.log | zeek-cut id.orig_h id.resp_h orig_bytes resp_bytes duration |
  awk '$3 > 100000000 { printf "%s %s upload_MB=%.0f dur=%.0fs\n", $1, $2, $3/1048576, $5 }' |
  sort -k3 -rn | head -15

O segundo exemplo usa conn_state para achar origens internas gerando volumes de recusa e de tentativa sem resposta, sinal clássico de varredura ou de serviço morto explorado por ferramenta.

cat conn.log | zeek-cut conn_state id.orig_h id.resp_h id.resp_p |
  awk '$1 == "REJ" || $1 == "S0" { print $2, $3, $4 }' |
  sort | uniq -c | sort -rn | head -20

No http.log, o filtro abaixo cruza método, tamanho de corpo enviado e destino, apontando uploads volumétricos e possíveis webshells, que a nota 14 detalha por campo.

cat http.log | zeek-cut method host uri request_body_len |
  awk '$1 == "POST" { print $4, $2, $3 }' | sort -rn | head -15

No files.log, o interesse recai sobre executáveis e contentores de script que atravessaram a rede em qualquer protocolo, inclusive SMTP e HTTP.

cat files.log | zeek-cut source mime_type sha256 total_bytes |
  awk '$2 == "application/x-dosexec" { print $3, $4, $1 }' | head -15

O weird.log é o log mais negligenciado e um dos mais baratos de caçar, porque registra o que o Zeek não conseguiu interpretar, exatamente onde ferramentas de evasão de protocolo deixam rastro.

cat weird.log | zeek-cut name source | sort | uniq -c | sort -rn | head -15

Nomes de weird repetidos em muitos hosts indicam anomalia de parser ou de firmware; nomes novos em um só host merecem o pcap correspondente. Cada achado por log deve voltar ao conn.log pelo uid ou pela 5-tupla, porque é no conn que duração, bytes e desfecho dão peso ao achado.

4. Suricata: EVE JSON e Regras Customizadas

O Suricata grava tudo em eve.json, um JSON por linha, com o campo event_type separando alert, dns, http, tls, fileinfo e flow. O alerta carrega signature_id, signature, severity e category, e cada evento de protocolo traz os campos específicos já normalizados, como http.http_user_agent e tls.sni. A manipulação padrão é feita com jq, e a agregação abaixo produz o ranking de alertas por assinatura com origem e destino, base da triagem diária e da escolha de sementes de caça.

jq -c 'select(.event_type=="alert") |
      {ts, src: .src_ip, dst: .dest_ip, sid: .alert.signature_id,
       sig: .alert.signature, sev: .alert.severity}' /var/log/suricata/eve.json |
  sort | uniq -c | sort -rn | head -20

As regras customizadas seguem a sintaxe clássica de IDS: cabeçalho com ação, protocolo, redes e portas, seguido das opções entre parênteses. Palavras-chave de buffer como http.method, http.uri e http.user_agent permitem regras cirúrgicas; detection_filter aplica limiar de frequência por origem, útil para cadência de beacon. Regras da casa usam sid a partir de 1000000, ficam em repositório versionado com msg descritiva e falsepositives anotados, e toda alteração passa por suricata -T -c /etc/suricata/suricata.yaml antes do deploy. A regra de exemplo abaixo dispara quando um host interno faz POST frequente para PHP com user agent de ferramenta de linha de comando, padrão compatível com webshell e com beacon de C2 em HTTP.

alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"CTH: POST recorrente para PHP com UA de ferramenta"; flow:established,to_server; http.method; content:"POST"; http.user_agent; content:"python-requests"; http.uri; pcre:"/\.php(\?|$)/"; detection_filter: track by_src, count 12, seconds 300; sid:1000004; rev:1;)

5. Combinando Zeek e Suricata na Prática

A combinação dos dois sensores no mesmo ponto de observação é o padrão recomendado, e o valor vem do fluxo de trabalho entre eles. O alerta de Suricata é a semente; os logs de Zeek entregam o contexto que a assinatura não carrega, como user agent, fingerprint TLS, domínio consultado e bytes transferidos. O caminho inverso também é valioso: padrão confirmado em caça vira regra de Suricata para detecção contínua. Como o flow_id do Suricata e o uid do Zeek não coincidem, a junção é feita pela 5-tupla com janela temporal curta, o que exige relógios sincronizados por NTP nos dois sensores.

Aspecto Zeek Suricata
Papel principal Entendimento de protocolo e log estruturado Assinatura em tempo real e alerta priorizado
Saída Logs TSV por protocolo, costurados por uid EVE JSON por evento, incluindo alert, dns, http, tls, fileinfo e flow
Força na caça Consultas iterativas e correlação por uid e 5-tupla Semente de hipótese e validação de IOC conhecido
Custo típico CPU de análise de protocolo em links altos CPU de inspeção de conteúdo e memória de regras

O script abaixo enriquece cada alerta do dia com o resumo das conexões correspondentes no conn.log, primeiro passo de qualquer triagem séria de alerta.

jq -r 'select(.event_type=="alert") | [.src_ip, .dest_ip] | @tsv' eve.json |
  sort -u | while IFS=$'\t' read -r s d; do
    echo "== $s para $d"
    cat conn.log | zeek-cut id.orig_h id.resp_h proto service duration orig_bytes resp_bytes conn_state |
      awk -v s="$s" -v d="$d" '$1==s && $2==d'
  done | head -60

6. Caso de Caça: Beaconing com conn.log

A hipótese do caso segue a gramática do capítulo 07: SE um host executa malware com C2 em HTTP ou HTTPS, ENTÃO ele estabelece conexões periódicas para um destino externo com pequena variância de intervalo, mapeando T1071 e T1041 do ATT&CK. O método é agrupar conexões por par origem, destino e porta, calcular intervalos entre conexões sucessivas e medir o coeficiente de variação. O script abaixo consome o extrato do conn.log preparado na primeira linha e imprime apenas pares com pelo menos vinte conexões, intervalo médio entre 20 e 900 segundos e variação abaixo de 10 por cento, limiares que excluem tráfego humano e sessões longas legítimas.

cat conn.log | zeek-cut ts id.orig_h id.resp_h id.resp_p orig_bytes resp_bytes > conn_flows.tsv
python3 hunt_beacon.py conn_flows.tsv
# hunt_beacon.py: pares com ritmo periodico e baixa variância de intervalo
import sys, statistics as st
from collections import defaultdict

par = defaultdict(list)
with open(sys.argv[1]) as f:
    for linha in f:
        campos = linha.split()
        if len(campos) < 6:
            continue
        par[(campos[1], campos[2], campos[3])].append(float(campos[0]))

for (origem, destino, porta), ts in sorted(par.items()):
    ts.sort()
    if len(ts) < 20:
        continue
    intervalos = [b - a for a, b in zip(ts, ts[1:])]
    media = st.mean(intervalos)
    if media <= 0:
        continue
    cv = st.pstdev(intervalos) / media
    if cv < 0.10 and 20 <= media <= 900:
        print(f"{origem} -> {destino}:{porta} n={len(ts)} media={media:.1f}s cv={cv:.3f}")

O caso realista da biblioteca: a caça aponta 10.20.7.51 com 60 conexões em uma hora para 203.0.113.44:443, média de 60,2 segundos e cv de 0,03. A qualificação puxa os demais logs pelo host e pela janela do dia, como abaixo, e verifica o que explica a comunicação.

cat dns.log | zeek-cut id.orig_h query rcode_name | awk '$1 == "10.20.7.51"' | tail -25
cat http.log | zeek-cut id.orig_h host uri user_agent | awk '$1 == "10.20.7.51"' | tail -25
cat ssl.log | zeek-cut id.orig_h server_name validation_status ja3 | awk '$1 == "10.20.7.51"' | tail -25

O resultado fecha o quadro: dns.log mostra cdn-metrics-update.net, domínio registrado nove dias antes; http.log mostra user agent python-requests com respostas 200 e corpos pequenos; ssl.log mostra ja3 ausente do baseline do segmento. Beacon legítimo fica descartado porque o destino não tem histórico corporativo, o user agent não casa com software inventariado e o intervalo é de relógio, não de cron documentado. O encaminhamento abre caso de IR com contenção do host, e o padrão vira duas detecções permanentes: regra de Suricata com o URI e o user agent, e consulta agendada no SIEM reproduzindo o script com os limiares do caso. Exclusões do baseline, como agentes de inventário e telemetria de SaaS documentados, entram versionadas para que a próxima execução produza menos ruído.

7. Erros Comuns e Pontos de Atenção

  • Caçar rede sem exclusões do baseline; efeito: fila de falsos positivos com agentes legítimos que também falam em intervalos regulares; correção: exceções versionadas com domínio, user agent e intervalo esperado.
  • Tratar o alerta de Suricata como veredito; efeito: fechamento precoce baseado em reputação limpa de IP reciclado; correção: enriquecer todo alerta com Zeek e endpoint antes de decidir.
  • Revisar consultas após atualização do Zeek; efeito: pipeline quebra em silêncio quando colunas mudam de ordem ou de nome; correção: conferir o cabeçalho #fields de cada log depois de cada upgrade.
  • Ignorar a diferença entre alerta e sessão; efeito: contagem de alertas substitui análise de comportamento e esconde mimetização; correção: sempre puxar o conn e os logs de protocolo da mesma janela.
  • Misturar fusos e relógios entre sensores; efeito: correlação por janela temporal falha e o achado parece inconsistente; correção: NTP em todos os sensores e verificação periódica de skew.
  • Escrever regras customizadas sem sid único e sem registro; efeito: conflito de assinaturas e regras órfãs sem dono; correção: repositório versionado com revisão, justificativa e data.
  • Deixar o weird.log fora da rotina; efeito: perda de sinais de evasão de protocolo e de falhas de parser que antecedem incidentes; correção: triagem semanal de nomes novos com retenção do pcap.

8. Integração com o Restante da Biblioteca

Esta nota é a camada de sensor do capítulo 08: a nota 13 estende a cobertura com fluxo onde o pacote completo não chega, e a nota 14 desce aos campos de DNS, HTTP e TLS que aqui aparecem apenas como âncora. O desenho de coleta e a justificativa de custos vivem na nota 04 do capítulo 02 e no controle DAT.2 do capítulo 15, e o processo de hipótese, execução e registro segue o capítulo 07, com o TH.12 do capítulo 15 definindo as salvaguardas por maturidade. A conversão de caças confirmados em regras permanentes é o trabalho do capítulo 18, e as métricas de cobertura e de tempo de detecção alimentam o capítulo 11.

Um detalhe operacional fecha a nota com ganho composto: trate cada script desta nota como item do catálogo de caça, com identificador da hipótese, dono, data da última execução e resultado resumido. Programas maduros rodam o beacon de conn.log em cadência fixa do ciclo PEAK e mantêm as exclusões vivas; o efeito acumulado é que cada execução custa menos e cobre mais, e a biblioteca de hunts da organização passa a valer mais do que qualquer feed isolado de IOC.