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

05 - Notebooks de Análise (Jupyter, Python, Pandas)

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

Notebooks de Análise (Jupyter, Python, Pandas)

Nota 05 do capítulo 09 (Ferramentas e Plataformas), que fecha o bloco com o ambiente onde a análise vai além da linguagem de consulta da plataforma.
Cobre Jupyter e JupyterLab com msticpy e pandas, os casos de uso clássicos de caça e as disciplinas de credenciais e reprodutibilidade.
Dialoga com as notas 01 a 04 deste capítulo, com os controles TH.8 e TH.9 do capítulo 15 e com o capítulo 18.

1. Contexto e Posicionamento na Biblioteca

As notas anteriores deste capítulo mostraram plataformas que consultam: SIEM, EDR e TIP, cada uma com linguagem própria e limites próprios. Esta nota apresenta o ambiente que não consulta, mas analisa: o notebook interativo, onde o hunter baixa dados de qualquer fonte, combina estatística, gráfico e narrativa no mesmo documento e transforma exploração ad hoc em código reutilizável. O Jupyter é o padrão de facto dessa modalidade, e o movimento de notebooks para threat hunting consolidou ferramentas como msticpy, da Microsoft, que embala consultas, enriquecimento e visualização para análise de segurança.

O papel do notebook no portfólio é específico e complementar. A consulta na plataforma responde rápido a perguntas estruturadas sobre o índice; o notebook responde às perguntas que exigem estatística sobre janelas longas, junção de fontes heterogêneas, aprendizado de máquina leve ou visualização sob medida. O erro oposto também existe: transferir para o notebook o que a linguagem de consulta resolve em uma linha, criando um segundo lugar para manter a mesma lógica. A fronteira saudável é estatística e integração no notebook, seleção e triagem na plataforma.

2. Jupyter e JupyterLab como Bancada de Caça

O Jupyter Notebook organiza o trabalho em células de código, de texto e de saída, executadas em qualquer ordem, sobre um kernel Python que mantém o estado entre execuções. O JupyterLab é a interface de trabalho completa, com explorador de arquivos, terminal integrado e painéis lado a lado, e é a instalação recomendada para bancada de caça. O valor para o hunter vem da combinação de três elementos no mesmo documento: o código que extrai e transforma dados, o gráfico que mostra o padrão e o texto que registra hipótese, veredicto e próximo passo, tudo versionável e compartilhável.

A instalação típica isola o ambiente por projeto com venv ou conda, expõe o serviço apenas na rede de análise ou no túnel, e guarda os datasets em diretório de trabalho dedicado. O bloco abaixo é a sequência padrão de preparo da bancada em estação Linux, do ambiente isolado à abertura do serviço apenas em interface local:

python3 -m venv ~/.venvs/hunt && source ~/.venvs/hunt/bin/activate
pip install jupyterlab pandas msticpy plotly scikit-learn
jupyter lab --no-browser --port 8888 --ip 127.0.0.1

Um servidor Jupyter no segmento de análise, com acesso ao SIEM por API e ao data lake da nota 02, permite que o hunter consulte meses de telemetria sem tocar na estação de trabalho do dia a dia. O notebook nunca substitui a plataforma de coleta; ele é a bancada que se liga às plataformas.

3. O Ecossistema Python: pandas, msticpy e Companhia

O pandas é o motor da análise tabular: DataFrame e Series oferecem filtro, agregação, junção, pivoteamento e janelas móveis com sintaxe consistente, e são o fundamento de praticamente toda caça em notebook. O msticpy adiciona a camada de segurança: provedores de consulta para Microsoft Sentinel, Microsoft Defender, Splunk e outras fontes, funções de enriquecimento de IP e domínio, decodificação de linha de comando do Windows, extração de IoC de texto e visualizações orientadas a incidente. O exemplo abaixo conecta ao workspace do Sentinel e puxa logons de um host específico com uma linha de consulta nomeada:

from msticpy.data import QueryProvider
from msticpy.config import WorkspaceConfig

qry_prov = QueryProvider("MSSentinel")
qry_prov.connect(WorkspaceConfig())
logons = qry_prov.WindowsSecurity.list_host_logons(
    host="FILE04", start="2025-11-01", end="2025-11-30"
)
logons.head()

O restante do cinto de ferramentas completa casos específicos: matplotlib e plotly para gráficos, scikit-learn para agrupamento e anomalia, networkx para grafos de comunicação e requests para APIs de enriquecimento como a do TIP da nota 04. A disciplina de import é parte da bancada: notebook que importa meia dúzia de bibliotecas sem arquivo de dependências não sobrevive à troca de máquina, tema que a seção de reprodutibilidade retoma.

4. Casos de Uso Clássicos: Stack Counting, Beaconing e Clustering

Os três casos que justificam o notebook são os controles TH.8 e TH.9 do capítulo 15 levados à estatística. O stack counting em notebook vai além da contagem da plataforma: compara distribuição atual com distribuição histórica, medindo quantos processos novos apareceram no parque em relação à semana base, com teste de proporção ou simples razão de frequência. O resultado é a lista de novidades do parque, que é a semente de caça mais barata que existe.

A estatística de beaconing é o caso mais clássico: para cada par origem e destino, ordenar os eventos no tempo, calcular intervalos entre conexões sucessivas e medir média, desvio e coeficiente de variação. O mesmo raciocínio do script Python da nota 12 do capítulo 08 roda em pandas sobre janelas de meses, com o acréscimo de visualização de histograma que distingue relógio de cron documentado. O fragmento abaixo é o núcleo dessa análise sobre um CSV de conexões com as colunas ts, origem e destino:

conex = pd.read_csv("conn_novembro.csv", parse_dates=["ts"]).sort_values("ts")
conex["intervalo"] = (
    conex.groupby(["origem", "destino"])["ts"].diff().dt.total_seconds()
)

ritmo = (
    conex.dropna(subset=["intervalo"])
    .groupby(["origem", "destino"])["intervalo"]
    .agg(n="count", media="mean", desvio="std")
)
ritmo["cv"] = ritmo["desvio"] / ritmo["media"]

candidatos = ritmo[
    (ritmo["n"] >= 20)
    & (ritmo["cv"] < 0.10)
    & ritmo["media"].between(20, 900)
]
print(candidatos.head(10))

Os limiares do fragmento são os mesmos calibrados no capítulo 08, ao menos vinte conexões, coeficiente de variação abaixo de 0,10 e intervalo médio entre 20 e 900 segundos, e a vantagem do pandas é a revisão desses limiares em célula própria, com histograma ao lado, antes de rodar sobre o mês inteiro. Limiar é decisão de caça, e decisão de caça se documenta, por isso os números vivem em célula separada e não embutidos no meio do código.

O clustering fecha o trio: agrupar hosts ou usuários por vetor de características, como destinos acessados, horas ativas e processos executados, com KMeans ou DBSCAN, e investigar os grupos pequenos e distantes do padrão majoritário. O produto do clustering não é a detecção em si, mas a lista curta de exceções que merece caça dirigida na semana seguinte.

5. Exemplo Prático: pandas sobre Logs de Autenticação

O exemplo abaixo analisa um mês de logs de autenticação exportados do SIEM em CSV, com as colunas timestamp, usuario, origem, destino e resultado, onde resultado guarda o código do evento, 4624 para sucesso e 4625 para falha. O primeiro bloco carrega o dado, valida o esquema e implementa o password spray (T1110.003) com os dois limiares que definem a hipótese: pelo menos dez contas distintas e vinte e cinco tentativas a partir da mesma origem:

import pandas as pd

df = pd.read_csv(
    "logons_novembro.csv",
    parse_dates=["timestamp"],
    dtype={"usuario": "string", "origem": "string",
           "destino": "string", "resultado": "string"},
)
assert df["timestamp"].notna().all(), "timestamp nulo indica quebra de exportacao"

falhas = df[df["resultado"] == "4625"]
spray = (
    falhas.groupby("origem")
    .agg(contas_distintas=("usuario", "nunique"),
         tentativas=("usuario", "size"),
         hosts_alvo=("destino", "nunique"))
    .query("contas_distintas >= 10 and tentativas >= 25")
    .sort_values("contas_distintas", ascending=False)
)
print(spray.head(10))

O segundo bloco caça o comportamento raro por conta: constrói a matriz conta por hora do dia, mede quantas horas distintas cada conta usa e aponta contas com muito volume concentrado em janelas fora do padrão do parque, que é a assinatura de conta comprometida operando fora do horário do dono:

df["hora"] = df["timestamp"].dt.hour
sucessos = df[df["resultado"] == "4624"]

matriz = pd.crosstab(sucessos["usuario"], sucessos["hora"])
horas_ativas = matriz.gt(0).sum(axis=1)
volume = matriz.sum(axis=1)

concentradas = (
    pd.DataFrame({"horas": horas_ativas, "eventos": volume})
    .query("horas <= 2 and eventos >= 30")
    .sort_values("eventos", ascending=False)
)
print(concentradas.head(10))
concentradas.plot(kind="bar", y="eventos", figsize=(12, 4))

A qualificação do achado segue o ciclo do capítulo 07: para cada conta da lista, puxar os logons bem-sucedidos da janela, verificar origem e destino, cruzar com o baseline do usuário e registrar veredicto. O notebook termina com o registro: célula de texto com hipótese, método, limiares, achado e encaminhamento, para que a próxima execução parta daqui e o capítulo 05 do capítulo 07 receba o material documentado.

6. Gestão de Credenciais e Segurança do Ambiente

O notebook que consulta SIEM, EDR e TIP manipula credenciais, e a disciplina começa por nunca escrever segredo em célula, porque a célula fica no histórico, no checkpoint e no repositório. O padrão seguro usa variáveis de ambiente carregadas de arquivo .env fora do controle de versão, ou keyring do sistema operacional, e cofre de segredos como Azure Key Vault ou HashiCorp Vault quando o ambiente corporativo fornece. O msticpy lê configuração de arquivo msticpyconfig.yaml com referências a Key Vault, e o exemplo abaixo carrega segredos do ambiente sem expor valor no notebook:

import os

misp_key = os.environ["MISP_API_KEY"]      # falha cedo se ausente
sentinel_ws = os.environ["SENTINEL_WORKSPACE_ID"]

As outras duas regras de segurança da bancada são sobre escopo e sobre dado. O escopo das chaves segue o princípio da nota 04: chave de leitura para consulta, chave separada e restrita para escrita, com rotação agendada. O dado baixado para o notebook herda a classificação do SIEM: dataset com TLP amber ou com dado pessoal regido pela LGPD não circula em exportação de notebook, e a retenção do diretório de trabalho é política explícita, com limpeza periódica, porque dataset acumulado em servidor de análise é o novo ponto de fuga que ninguém inventariou.

7. Reprodutibilidade e Versionamento

A caça em notebook que não reproduz é opinião, e a reprodutibilidade tem quatro requisitos práticos. O primeiro é o ambiente congelado: requirements.txt gerado por pip freeze ou environment.yml do conda, versionado junto do notebook, de preferência com versões pinadas. O segundo é o versionamento do notebook em repositório Git, com a conversão de notebooks em .py via Jupytext ou nbconvert quando o time adotar revisão por pares em pull request, porque diff de JSON de notebook é ilegível. O terceiro é a ordem de execução: executar Kernel e depois Restart and Run All antes de publicar, garantindo que o documento roda do topo ao fim sem depender de células executadas fora de ordem. O quarto é o dado: dataset de entrada com hash ou com consulta registrada, porque o achado vale só enquanto a entrada é reconstituível.

A automação fecha o ciclo de maturidade: papermill executa o notebook parametrizado em agenda, transformando a caça interativa em caça contínua do capítulo 07, e a saída versionada alimenta a métrica de execução do capítulo 11. O comando abaixo é o esqueleto dessa execução, com o mês e o limiar passados como parâmetros de célula marcada:

papermill hunt_beacon.ipynb saida/hunt_beacon_2025_11.ipynb \
  -p mes "2025-11" -p limiar_cv 0.10

O padrão maduro guarda dois artefatos por caça: o notebook de exploração, com o caminho analítico completo, e o notebook parametrizado de produção, limpo de iterações mortas, que roda na agenda com o mesmo código validado. O primeiro preserva o raciocínio para auditoria e aprendizado; o segundo preserva o custo previsível da execução rotineira.

8. Limitações e Erros Comuns

O notebook tem limites estruturais que definem onde ele não deve ser usado. Ele não é motor de busca: puxar o índice inteiro do SIEM para o pandas é desperdício, quando o filtro inicial roda na plataforma e o notebook recebe o recorte. Ele não é tempo real: análise de notebook roda sobre dado já coletado, e a latência de detecção contínua é território da plataforma. Ele não escala por si: DataFrame que cresce sem controle estoura a memória do kernel, e o caminho de escala é amostragem, agregação prévia no SIEM ou motor distribuído como Spark e DuckDB.

  • Escrever segredo em célula; efeito: credencial no histórico, no checkpoint e no Git; correção: variáveis de ambiente, keyring ou cofre, com varredura de repositório por segredo.
  • Publicar notebook que não roda do topo; efeito: colega executa e o resultado diverge, ou a célula falha por estado inexistente; correção: Restart and Run All como critério de publicação.
  • Manter dataset de produção no diretório do notebook para sempre; efeito: acervo de dados sensíveis fora do controle de retenção; correção: diretório de trabalho com política de limpeza e dado classificado.
  • Duplicar no notebook a lógica que já roda na plataforma; efeito: duas versões da mesma hipótese divergindo com o tempo; correção: fronteira clara, seleção e triagem na plataforma, estatística e integração no notebook.
  • Tratar o resultado do notebook como veredicto imediato; efeito: anomalia estatística encerrada como incidente sem qualificação no contexto; correção: mesmo achado estatístico segue o ciclo de qualificação do capítulo 07.
  • Rodar notebook com dependências do sistema; efeito: caça que funciona só na máquina do autor e morre na troca; correção: ambiente isolado e congelado por projeto, versionado no repositório.

9. Integração com o Restante da Biblioteca

Esta nota fecha a camada de ferramentas do capítulo 09: o SIEM da nota 01 e o EDR da nota 02 fornecem os dados, as linguagens da nota 03 resolvem a seleção e o TIP da nota 04 fornece o enriquecimento que o notebook consome por API. As técnicas estatísticas aqui implementadas são os controles TH.8 e TH.9 do capítulo 15, o fluxo de hipótese e registro segue o capítulo 07, e a promoção da análise validada em detecção permanente ou em caça agendada com papermill é o trabalho dos capítulos 18 e 07, respectivamente. As métricas de execução e de valor das caças em notebook alimentam o capítulo 11.

Um detalhe final fecha o capítulo com o ativo que sobrevive às ferramentas: o notebook versionado é a forma mais completa de documentação de caça que a biblioteca conhece, porque código, dado, gráfico e raciocínio moram no mesmo arquivo. Programas maduros tratam o repositório de notebooks como o segundo corpo da biblioteca de caças, ao lado do catálogo Sigma da nota 03, e o efeito composto é o mesmo: cada caça nova custa menos, porque começa do código, do dado e do veredicto da anterior.