Como montar um SIEM doméstico no Linux e vigiar sua rede

Descubra como construir um micro-SIEM caseiro no Linux para monitorar intrusos e novos dispositivos na rede local com varredura ARP sem pesar na CPU.

Compartilhar
Como montar um SIEM doméstico no Linux e vigiar sua rede

Se você administra um servidor em casa, mantém um homelab ou simplesmente possui dezenas de aparelhos conectados na sua residência, com certeza já viveu esta cena: abrir a interface web do roteador da operadora e se deparar com uma lista de vinte endereços MAC misteriosos.

A tela do roteador raramente ajuda. Metade dos itens aparece com o nome "Desconhecido", "Wireless-Device" ou com um campo em branco. Você fica na dúvida se aquele endereço pertence à lâmpada inteligente da sala, ao leitor de e-book da sua família ou a um vizinho que conseguiu a senha do seu Wi-Fi.

Os firmwares padrão de roteadores domésticos são lentos para atualizar concessões de DHCP, não avisam quando um aparelho se conecta e não identificam o fabricante do hardware. Por outro lado, ferramentas corporativas de monitoramento como Wazuh ou Splunk exigem servidores pesados, muita memória RAM e configurações complexas demais para quem só quer saber o que acontece na sala de estar.

Neste artigo, você vai aprender a montar um sentinela de rede doméstico (um micro-SIEM) extremamente enxuto no Linux. A solução foi inspirada em uma implementação compartilhada pelo desenvolvedor Evandro Carvalho na comunidade técnica e utiliza recursos nativos do próprio sistema operacional para vigiar a rede local gastando quase zero de processamento.

O que é um SIEM e por que ter uma versão caseira?

A sigla SIEM significa Security Information and Event Management (Gerenciamento de Eventos e Informações de Segurança). No mundo corporativo, é a central que coleta registros de todos os servidores, firewalls e computadores para alertar quando algo suspeito acontece.

Em casa ou em um homelab modesto, você não precisa de uma central gigantesca. O seu objetivo principal é muito mais direto:

  1. Saber exatamente quem está conectado na sua rede local.
  2. Identificar qual empresa fabricou a placa de rede daquele aparelho.
  3. Receber um alerta imediato no instante em que um dispositivo desconhecido aparecer.

Se você já acompanhou nosso artigo sobre o que é self-host e por que você deveria se importar, sabe que ter controle sobre seus próprios dados e infraestrutura começa dentro da sua própria casa. Da mesma forma que mostramos os riscos de aparelhos inteligentes em sua Smart TV está vendendo sua internet: como se proteger, manter os olhos abertos na camada de rede é essencial.

O erro comum: por que rodar Nmap em loop é uma má ideia

A primeira reação de muitos administradores quando pensam em varrer a rede local é criar um script em shell com o Nmap rodando a cada dois minutos:

# Evite fazer isso em loop contínuo
nmap -sn 192.168.1.0/24

Embora o Nmap seja fantástico para auditorias pontuais, usá-lo como monitor contínuo em redes domésticas traz dois problemas sérios:

  1. Ruído e consumo de recursos: Disparar varreduras completas a cada minuto gera tráfego contínuo desnecessário e eleva o uso de CPU em servidores modestos (como Raspberry Pi ou mini PCs).
  2. Instabilidade em aparelhos de IoT: Lâmpadas inteligentes, interruptores e tomadas conectados via chips ESP8266 ou ESP32 possuem pilhas de rede muito frágeis. Uma chuva constante de pacotes SYN e probes ARP pode sobrecarregar a memória desses microcontroladores, fazendo com que eles reiniciem ou percam a conexão com o Home Assistant.

A sacada inteligente: aproveitando o kernel do Linux e o ARP

Em redes locais Ethernet e Wi-Fi, toda comunicação entre máquinas utiliza o protocolo ARP (Address Resolution Protocol) para traduzir um endereço IP (como 192.168.1.50) em um endereço físico MAC (como DC:A6:32:11:22:33).

O próprio kernel do Linux mantém uma tabela com todas essas correspondências na memória RAM. Essa tabela pode ser consultada a qualquer momento lendo um arquivo virtual do sistema:

cat /proc/net/arp

A saída desse comando mostra os endereços IP ativos, os respectivos endereços MAC e a interface de rede:

IP address       HW type     Flags       HW address            Mask     Device
192.168.1.1      0x1         0x2         e4:ca:12:34:56:78     *        eth0
192.168.1.105    0x1         0x2         dc:a6:32:88:99:aa     *        eth0
192.168.1.142    0x1         0x2         ec:fa:bc:12:77:88     *        eth0

Ler o arquivo /proc/net/arp consome 0% de CPU porque o arquivo não está no disco, mas diretamente na memória gerenciada pelo kernel.

Para garantir que a tabela esteja sempre atualizada mesmo para dispositivos em modo de economia de energia, basta disparar um ping sweep assíncrono e suave a cada três ou cinco minutos. Disparar uma rajada de pings simultâneos usando asyncio em Python leva menos de dois segundos na sub-rede inteira e não sobrecarrega os dispositivos menores.

Como identificar o fabricante: o padrão IEEE OUI

Todo endereço MAC é composto por seis pares de caracteres hexadecimais (octetos). Os três primeiros pares (por exemplo, DC:A6:32) formam o chamado OUI (Organizationally Unique Identifier).

Essa sequência é registrada oficialmente no comitê internacional IEEE por cada fabricante de hardware:

  • DC:A6:32 pertence à Raspberry Pi Foundation.
  • EC:FA:BC e 30:AE:A4 pertencem à Espressif Systems (os famosos chips de lâmpadas Tuya, Sonoff e projetos DIY).
  • AC:D1:B8 pertence à Apple.
  • 24:4C:E3 pertence à Samsung.
  • 18:66:DA pertence à Intel.

Ao cruzar os primeiros caracteres do endereço MAC com um dicionário local, o nosso script descobre na hora se o novo hóspede da rede é um iPhone, um notebook de trabalho ou um dispositivo IoT recém-instalado, sem precisar fazer nenhuma consulta em APIs lentas na internet.

Construindo o sentinela em Python

Vamos montar o script que fará o trabalho de vigilância. Crie uma pasta para o projeto no seu servidor Linux:

mkdir -p ~/sentinela-rede
cd ~/sentinela-rede

Crie o arquivo monitor_rede.py:

import os
import json
import asyncio
from datetime import datetime

# Dicionario basico de fabricantes OUI para resolucao offline imediata
OUI_DATABASE = {
    "DC:A6:32": "Raspberry Pi Foundation",
    "B8:27:EB": "Raspberry Pi Foundation",
    "E4:5F:01": "Raspberry Pi Foundation",
    "EC:FA:BC": "Espressif (Tuya / Smart Life / IoT)",
    "30:AE:A4": "Espressif Systems (ESP32)",
    "24:62:AB": "Espressif Systems (ESP8266)",
    "AC:D1:B8": "Apple, Inc.",
    "F0:18:98": "Apple, Inc.",
    "24:4C:E3": "Samsung Electronics",
    "B0:72:BF": "Samsung Electronics",
    "18:66:DA": "Intel Corporation",
    "00:1A:2B": "Cisco Systems",
    "E4:CA:12": "Roteador / Gateway Principal",
}

KNOWN_DEVICES_FILE = os.path.expanduser("~/sentinela-rede/dispositivos_conhecidos.json")

def resolve_vendor(mac: str) -> str:
    """Extrai os tres primeiros octetos e identifica o fabricante."""
    prefix = mac.upper()[:8]
    return OUI_DATABASE.get(prefix, "Fabricante nao identificado")

def get_arp_devices() -> list:
    """Le a tabela ARP mantida pelo kernel do Linux na memoria."""
    devices = []
    arp_path = "/proc/net/arp"
    
    if not os.path.exists(arp_path):
        return devices

    with open(arp_path, "r") as f:
        lines = f.readlines()
        # Ignora a primeira linha de cabecalho
        for line in lines[1:]:
            parts = line.split()
            # Valida formato e descarta entradas incompletas (0x0) ou zeradas
            if len(parts) >= 6 and parts[2] == "0x2" and parts[3] != "00:00:00:00:00:00":
                ip = parts[0]
                mac = parts[3].upper()
                interface = parts[5]
                devices.append({
                    "ip": ip,
                    "mac": mac,
                    "interface": interface,
                    "vendor": resolve_vendor(mac)
                })
    return devices

async def ping_sweep(subnet: str = "192.168.1"):
    """Dispara pings assincronos em lote na sub-rede para forcar atualizacao do ARP."""
    async def ping_host(ip: str):
        proc = await asyncio.create_subprocess_exec(
            "ping", "-c", "1", "-W", "1", ip,
            stdout=asyncio.subprocess.DEVNULL,
            stderr=asyncio.subprocess.DEVNULL
        )
        await proc.wait()

    tasks = [ping_host(f"{subnet}.{i}") for i in range(1, 255)]
    await asyncio.gather(*tasks)

def carregar_conhecidos() -> dict:
    if os.path.exists(KNOWN_DEVICES_FILE):
        try:
            with open(KNOWN_DEVICES_FILE, "r") as f:
                return json.load(f)
        except Exception:
            return {}
    return {}

def salvar_conhecidos(dados: dict):
    with open(KNOWN_DEVICES_FILE, "w") as f:
        json.dump(dados, f, indent=2)

def alertar_novo_dispositivo(device: dict):
    horario = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    mensagem = (
        f"[ALERTA DE INTRUSO/NOVO APARELHO]\n"
        f"Horario: {horario}\n"
        f"IP: {device['ip']}\n"
        f"MAC: {device['mac']}\n"
        f"Fabricante: {device['vendor']}\n"
        f"Interface: {device['interface']}\n"
        f"----------------------------------------"
    )
    print(mensagem)
    # Dica: aqui voce pode disparar um webhook para Discord, Telegram ou Home Assistant

async def ciclo_monitoramento(subnet: str = "192.168.1"):
    print(f"Iniciando ciclo de varredura na sub-rede {subnet}.0/24...")
    
    # 1. Atualiza a tabela ARP enviando pings assincronos
    await ping_sweep(subnet)
    
    # 2. Le as entradas ativas do kernel
    dispositivos_atuais = get_arp_devices()
    conhecidos = carregar_conhecidos()
    
    houve_alteracao = False

    for dev in dispositivos_atuais:
        mac = dev["mac"]
        if mac not in conhecidos:
            alertar_novo_dispositivo(dev)
            conhecidos[mac] = {
                "ip": dev["ip"],
                "vendor": dev["vendor"],
                "primeira_aparicao": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                "nome_personalizado": ""
            }
            houve_alteracao = True
        else:
            # Atualiza o ultimo IP visto caso tenha mudado pelo DHCP
            if conhecidos[mac].get("ip") != dev["ip"]:
                conhecidos[mac]["ip"] = dev["ip"]
                houve_alteracao = True

    if houve_alteracao:
        salvar_conhecidos(conhecidos)

    print(f"Varredura concluida. {len(dispositivos_atuais)} dispositivos online registrados.")

if __name__ == "__main__":
    # Ajuste aqui a faixa de IP correspondente ao seu roteador (ex: 192.168.0 ou 192.168.1)
    asyncio.run(ciclo_monitoramento(subnet="192.168.1"))

Testando a execução manual

Antes de criar um serviço permanente, teste a execução direta no terminal:

python3 monitor_rede.py

Na primeira execução, o script criará o arquivo dispositivos_conhecidos.json e listará todos os aparelhos encontrados. Se um novo aparelho entrar no Wi-Fi minutos depois e você rodar o script novamente, o alerta será impresso no terminal apontando o endereço IP, o MAC e a fabricante associada.

Para ambientes onde você organiza hosts locais com nomes legíveis, combinar essa prática com o que ensinamos em como fazer o Linux reconhecer um nome de domínio local como um endereço IP permite identificar instantaneamente quem é quem.

Rodando 24 horas por dia com o Systemd

Para que o monitoramento seja contínuo e silencioso, podemos transformar o script em um serviço do systemd acompanhado de um timer que executa a cada 5 minutos.

Primeiro, crie o arquivo de serviço:

sudo nano /etc/systemd/system/sentinela-rede.service

Cole o conteúdo abaixo (lembre-se de trocar seu_usuario pelo nome de usuário correto da sua máquina):

[Unit]
Description=Sentinela de Rede Domestica ARP
After=network.target

[Service]
Type=oneshot
User=seu_usuario
WorkingDirectory=/home/seu_usuario/sentinela-rede
ExecStart=/usr/bin/python3 /home/seu_usuario/sentinela-rede/monitor_rede.py

[Install]
WantedBy=multi-user.target

Agora, crie o timer correspondente para executar o script em intervalos regulares:

sudo nano /etc/systemd/system/sentinela-rede.timer

Insira as instruções de agendamento:

[Unit]
Description=Executa o Sentinela de Rede a cada 5 minutos

[Timer]
OnBootSec=1min
OnUnitActiveSec=5min
Unit=sentinela-rede.service

[Install]
WantedBy=timers.target

Ative e inicie o timer no sistema:

sudo systemctl daemon-reload
sudo systemctl enable --now sentinela-rede.timer

Para verificar se o timer está agendado e funcionando perfeitamente:

systemctl list-timers --all | grep sentinela

E para acompanhar os registros do sistema:

journalctl -u sentinela-rede.service -n 50 --no-pager

Como avançar com automações e quarentena

Com esse micro-sentinela funcionando na sua rede local, você tem em mãos uma base sólida para criar automações inteligentes:

  • Notificações em mensageiros: Você pode adicionar poucas linhas na função alertar_novo_dispositivo para enviar uma requisição HTTP via urllib.request ou requests para um bot no Telegram ou um webhook no Discord.
  • Integração com Home Assistant: É possível enviar eventos via MQTT para acender uma luz na cor vermelha caso um dispositivo de fabricante desconhecido conecte de madrugada.
  • Isolamento de porta: Em switches gerenciáveis, o evento pode disparar comandos automáticos para isolar uma VLAN ou bloquear conexões não autorizadas.

E se o seu servidor Linux estiver exposto a conexões externas além da rede local, não deixe de conferir também nosso checklist completo de hardening de SSH para garantir que, além do Wi-Fi, suas portas de acesso administrativo continuem trancadas.


Referências e Leituras Recomendadas