
CLI/TUI em Python para triagem forense de sistemas Ubuntu — detecta e corrige mecanismos de persistência com coleta de artefatos, correlação de linha do tempo e integração com Wazuh.
Quando você suspeita que um sistema Linux foi comprometido, os primeiros 30-40 minutos geralmente são gastos executando os mesmos dez comandos em sequência: verificar processos em execução, procurar tarefas cron estranhas, fazer grep de LD_PRELOAD, escanear authorized_keys em busca de novas entradas, auditar sudoers. Cada etapa é manual, com troca de contexto, e propensa a erros sob pressão. Perca uma fonte — digamos, /etc/sudoers.d/ em vez de apenas /etc/sudoers, ou crontabs de usuário além de /etc/cron.d/ — e você terá um panorama incompleto.
As opções existentes não resolvem isso de forma limpa. O lynis é um auditor de hardening, não uma ferramenta de triagem — ele relata fraquezas de configuração em um sistema limpo e gera ruído em um sistema comprometido. O chkrootkit e o rkhunter verificam assinaturas de rootkits conhecidos, mas são cegos a técnicas de persistência novas, como timers systemd abusados ou entradas cron com aparência legítima. Consultas genéricas de SIEM exigem infraestrutura de logs que pode não existir no sistema que você está analisando. E suítes forenses como o Volatility visam imagens de memória, não um shell ativo em um host em execução.
A lacuna é uma ferramenta que roda no sistema em execução agora mesmo, cobre os vetores de persistência mais comuns, correlaciona atividade entre fontes de log em uma linha do tempo e diz exatamente o que examinar — sem exigir um agente externo, um banco de dados ou uma conexão com a internet.
O ubuntils roda em quatro estágios sequenciais:
/proc, tabelas cron, unidades systemd, chaves SSH, arquivos sudoers, definições de ambiente, integridade de pacotes (dpkg --verify), configuração de PAM/NSS e módulos de kernel carregados. Leva cerca de 2,5 segundos em um sistema típico.--rules — sobre os artefatos coletados, produzindo uma lista de achados classificada e com pontuação de confiança em cerca de um segundo.--json).O próprio ubuntils não faz chamadas de rede, e todos os recursos — incluindo regras personalizadas e correlação — rodam contra artefatos coletados localmente. A única forma de os achados saírem do host é a integração com o Wazuh: se um agente Wazuh estiver instalado, um scan ao vivo grava seus achados em um arquivo local que o agente então envia ao seu manager. Passe --no-wazuh para desativar isso em uma execução.
O ubuntils scan permanece inalterado por tudo abaixo — ele continua 100% ao vivo, em host único, e todos os flags existentes funcionam de forma idêntica. Dois comandos adicionais, collect e analyze, dividem o mesmo pipeline de detecção/linha do tempo em um fluxo de trabalho de adquirir-e-analisar amigável para uso offline, para casos em que você não pode (ou não quer) executar a detecção diretamente no host sob investigação — veja Análise offline: collect e analyze abaixo, incluindo suas ressalvas sobre cobertura de detecção.
Ubuntu 22.04+ e qualquer sistema com PEP 668 (recomendado):
O Ubuntu 22.04+ bloqueia pip install em todo o sistema. Use o pipx — ele lida com o ambiente de forma transparente, para que você nunca precise pensar nisso:```bash
sudo apt install pipx -y
cd ubuntils
pipx install -e .
ubuntils scan
**Sistemas mais antigos / instalação manual:**```bash
git clone https://github.com/asmitdesai/ubuntils.git
cd ubuntils
pip install -r requirements.txt -e .
ubuntils scan
ubuntils requer root para acesso completo aos artefatos. Se você executar ubuntils scan como um usuário não-root, ele se re-invocará automaticamente com sudo usando o mesmo interpretador Python (por caminho absoluto), para que o ambiente correto seja usado sem encaminhar seu PATH para o processo root. Todo comando externo (ss, dpkg, systemctl, …) é resolvido em um caminho de busca fixo, pertencente ao root, nunca o seu PATH. Executar sem root ignorará /etc/shadow, algumas entradas de /proc e arquivos cron protegidos, e registrará avisos para cada um.
Somente detecção — TUI interativa:```bash sudo ubuntils scan
**Detecção com saída JSON salva em arquivo:**```bash
sudo ubuntils scan --json > /tmp/triage-$(hostname)-$(date +%Y%m%d).json
Detecção com pré-visualização de remediação via CLI (dry run — nenhuma alteração aplicada):```bash sudo ubuntils scan --remediate
**Detecção com remediação via CLI aplicada:**```bash
sudo ubuntils scan --remediate --confirm
Versão para impressão:```bash ubuntils version
**Colete um pacote à prova de adulteração para análise posterior ou offline:**```bash
sudo ubuntils collect --output /path/to/bundle.tar.gz
Analisar um pacote previamente coletado (não requer root):```bash ubuntils analyze /path/to/bundle.tar.gz --json
**Analise uma imagem forense montada ou uma árvore de sistema de arquivos extraída em vez de um pacote:**```bash
ubuntils analyze --root /mnt/forensic-image --json
Consulte Análise offline: coletar e analisar para o formato do bundle e — o mais importante — o que a análise offline não consegue detectar em comparação com um scan ao vivo.
ubuntils scan [OPTIONS] --json Output JSON to stdout instead of launching the TUI --output FILE Write the JSON report to FILE (implies --json) --remediate Run the remediation engine after detection --confirm Required with --remediate to actually apply changes (else dry-run) --min-confidence N Only auto-remediate findings with confidence >= N (default 40) --no-wazuh Never forward findings to a local Wazuh agent --config FILE YAML allowlist of findings to suppress (see below) --baseline FILE YAML baseline of environment-specific known-good fingerprints to suppress (see below) --since TIME Limit the timeline to events since TIME (e.g. '24h', '7d', '2026-05-20') --rules FILE YAML file of custom detection rules to add (see below) --verbose Verbose structlog output
ubuntils collect [OPTIONS] --output FILE Bundle path to write (default ./ubuntils-bundle-.tar.gz) --verbose Verbose structlog output
ubuntils analyze (BUNDLE | --root PATH) [OPTIONS] --root PATH Analyze a mounted image / artifact tree instead of a bundle --json Output JSON instead of launching the TUI --output FILE Write the JSON report to FILE (implies --json) --config FILE YAML allowlist of findings to suppress (same format as scan) --baseline FILE YAML baseline of environment-specific known-good fingerprints to suppress (same format as scan) --rules FILE YAML file of custom detection rules to add (same format as scan) --since TIME Limit the timeline to events since TIME (e.g. '24h', '7d', '2026-05-20') --verbose Verbose structlog output
ubuntils version Print version string and exit
### Allowlisting de falsos positivos (`--config`)
Um host recém-provisionado ou gerenciado por CI gera ruído esperado — chaves de deploy, crontabs de provisionamento, shell init embutido. Em vez de ensinar os respondedores a filtrá-lo mentalmente, suprima-o explicitamente com uma allowlist YAML:```yaml
# allowlist.yaml
allowlist:
rules:
- SHELL_RC_MODIFICATION # suppress this rule entirely
paths:
- /home/ci/.ssh/authorized_keys # suppress any finding on this exact path
Please provide the Markdown content to translate.```bash sudo ubuntils scan --json --config allowlist.yaml
A supressão é sempre explícita — por id de regra e/ou caminho exato do artefato. Não existe um interruptor genérico de "ignorar tudo". Um exemplo está em [`examples/allowlist.yaml`](https://github.com/asmitdesai/ubuntils/blob/main/examples/allowlist.yaml).
### Baseline de itens conhecidos como bons (`--baseline`)
`--config` suprime uma regra ou caminho *em todos os lugares, para qualquer pessoa que execute o ubuntils contra este código-fonte*. `--baseline` é mais restrito e específico do ambiente: ele diz "neste ambiente, este artefato exato — esta chave SSH, este arquivo RC — é conhecido como bom", sem silenciar a regra ou o caminho para todos os outros hosts que você escaneia com a mesma ferramenta. Mantido como um arquivo separado de `--config` pelo mesmo motivo pelo qual `--rules` é separado: supressão e allowlisting de regras inteiras são preocupações diferentes que não deveriam ficar em um único arquivo.```yaml
# baseline.yaml
baseline:
- rule_id: SSH_UNAUTHORIZED_KEY
fingerprint: ci@ci-runner # substring match against the finding's raw_value
- rule_id: SHELL_RC_MODIFICATION
fingerprint: /home/deploy/.bashrc # exact match against the finding's artifact_path
O Model Context Protocol (MCP) é um padrão aberto que permite que aplicações de IA se conectem de forma segura a ferramentas e fontes de dados externas. O MCP Inspector é uma ferramenta de desenvolvedor para testar e depurar servidores MCP.
O MCP Inspector é uma ferramenta de desenvolvedor para testar e depurar servidores MCP. Ele fornece uma interface web para:
npx @modelcontextprotocol/inspector
npm install -g @modelcontextprotocol/inspector
git clone https://github.com/modelcontextprotocol/inspector.git
cd inspector
npm install
npm run build
npx @modelcontextprotocol/inspector
Isso iniciará o servidor do Inspector e abrirá uma interface web em http://localhost:6274.
Você pode se conectar a um servidor MCP de várias maneiras:
npx @modelcontextprotocol/inspector node server.js
npx @modelcontextprotocol/inspector --transport sse --url http://localhost:3000/sse
npx @modelcontextprotocol/inspector --transport http --url http://localhost:3000/mcp
| Opção | Descrição |
|---|---|
--transport | Tipo de transporte (stdio, sse, http) |
--url | URL do servidor (para sse e http) |
--port | Porta para o servidor do Inspector (padrão: 6274) |
--config | Caminho para o arquivo de configuração |
--help | Mostrar ajuda |
Liste e visualize recursos expostos pelo servidor MCP.
Visualize e teste prompts fornecidos pelo servidor.
Execute ferramentas com parâmetros personalizados e visualize os resultados.
Monitore notificações e logs do servidor em tempo real.
Você pode configurar o Inspector usando um arquivo de configuração:
{
"mcpServers": {
"my-server": {
"command": "node",
"args": ["server.js"],
"env": {
"API_KEY": "your-api-key"
}
}
}
}
git clone https://github.com/modelcontextprotocol/inspector.git
cd inspector
npm install
npm run dev
npm test
npm run build
npm cache clean --forceMIT```bash sudo ubuntils scan --json --baseline baseline.yaml
Uma entrada de linha de base corresponde por `rule_id` mais um `fingerprint` testado como uma substring do `raw_value` do achado ou uma correspondência exata contra o seu `artifact_path`. Uma correspondência remove o achado do relatório por completo — a supressão nunca é silenciosa, no entanto: o número de achados que uma linha de base removeu é sempre visível em `scan_metadata.suppressed_by_baseline`. A supressão por allowlist (`--config`) continua a aplicar-se por cima da supressão por linha de base. Funciona de forma idêntica em `scan` e `analyze`. Um exemplo encontra-se em [`examples/baseline.yaml`](https://github.com/asmitdesai/ubuntils/blob/main/examples/baseline.yaml).
### Regras de deteção personalizadas (`--rules`)
`--config` *suprime* achados; `--rules` *adiciona-os*. São deliberadamente ficheiros separados porque são preocupações opostas.
Um ficheiro de regras é apenas de correspondência de padrões — sem expressões, sem condicionais e sem execução de código, pelo que carregar um nunca pode executar lógica fornecida por um atacante. Cada regra nomeia um `source` de artefacto, um modo `match` e um `pattern`:```yaml
# custom_rules.yaml
rules:
- id: CUSTOM_KNOWN_MINER
severity: HIGH # HIGH | MEDIUM | LOW
title: Known cryptominer in process cmdline
description: A running process command line matches a known miner.
source: process # cron | environment | ssh | process | network
match: substring # regex | substring | glob
pattern: xmrig
git clone https://github.com/example/tool.git
cd tool
pip install -r requirements.txt
python tool.py --target example.com
``````bash
sudo ubuntils scan --json --rules custom_rules.yaml
source | Correspondência com |
|---|---|
cron | O comando cron (caminho: o arquivo crontab) |
environment | A linha bruta de ambiente/inicialização de shell (caminho: o arquivo que a define) |
ssh | Tipo de chave, dados da chave e comentário (caminho: o arquivo authorized_keys) |
process | A linha de comando do processo (caminho: o caminho do executável) |
network | A descrição da conexão (caminho: remote_addr:remote_port) |
regex e substring correspondem à coluna de texto; glob corresponde à coluna de caminho — portanto, um glob de network como 203.0.113.*:* tem como alvo o endpoint remoto. Os achados de regras personalizadas são apenas sinalizadores (nunca remediados automaticamente) e ainda estão sujeitos à supressão por --config. Um exemplo está em examples/custom_rules.yaml.
Todo relatório --json carrega um campo report_sha256 — um SHA-256 sobre o conteúdo canônico do relatório. Isso torna um artefato de triagem coletado à prova de adulteração e permite referenciar uma varredura específica por digest em um arquivo de caso. O relatório também registra tool_version, hostname e um timestamp UTC generated_at em scan_metadata. Para scan, hostname/ubuntu_version descrevem a máquina em que o ubuntils está sendo executado; para analyze --root, eles são lidos do próprio /etc/hostname e /etc/os-release da imagem. Para analyze BUNDLE, eles vêm, em vez disso, do próprio manifesto do bundle — o host que foi coletado, não o host que executa o analyze — juntamente com collection_run_id e o collected_at_utc_start/collected_at_utc_end da coleta, de modo que o registro de cadeia de custódia do relatório segue a evidência em vez da estação de trabalho do analista.
Verificando um relatório. O relatório é emitido em forma canônica (chaves ordenadas, indentação de 2 espaços), e o digest cobre tudo exceto o próprio report_sha256:```python
import hashlib, json
doc = json.load(open("report.json"))
claimed = doc.pop("report_sha256")
assert hashlib.sha256(json.dumps(doc, indent=2, sort_keys=True).encode()).hexdigest() == claimed
---
## Análise offline: coletar e analisar
`ubuntils scan` executa coleta, detecção e linha do tempo juntos no host ativo. `collect` e `analyze` dividem esse pipeline em dois: `collect` adquire um pacote à prova de adulteração de um host (nenhuma detecção é executada), e `analyze` executa o mesmo pipeline de detecção/linha do tempo usado por `scan` contra um pacote, ou contra uma imagem montada via `--root`, sem exigir root e sem tocar novamente no host original. Isso é para casos em que você deseja adquirir artefatos uma vez e analisá-los depois, em outro lugar, ou repetidamente — ou quando você está fazendo triagem de uma imagem de disco em vez de um sistema em execução.
### `ubuntils collect````bash
sudo ubuntils collect --output /path/to/bundle.tar.gz
Requer root, como scan. Lê uma lista fixa de arquivos (/etc/passwd, /etc/group, /etc/shadow, /etc/sudoers, /etc/ld.so.preload, /etc/environment, /etc/crontab, /etc/profile, /var/log/syslog, /var/log/messages, /var/log/audit/audit.log) e executa uma lista fixa de comandos (ss -tunap, netstat -tunap, systemctl list-timers em formato JSON e texto, e journalctl -o json dos últimos 7 dias), calcula o hash de cada item capturado e grava tudo, juntamente com um manifest.json, num pacote .tar.gz. Os arquivos de log capturados e a saída do journalctl são o que permitem ao analyze BUNDLE construir uma linha temporal real offline. Se --output for omitido, o pacote é gravado em ./ubuntils-bundle-<UTC timestamp>.tar.gz no diretório atual.
ubuntils analyze BUNDLE.tar.gz [--json] [--output FILE] [--config FILE] [--baseline FILE] [--rules FILE] [--since TIME] ubuntils analyze --root /mnt/forensic-image [--json] [--output FILE] [--config FILE] [--baseline FILE] [--rules FILE] [--since TIME]
Recebe um caminho de bundle como argumento posicional ou `--root PATH` apontando para uma imagem montada / árvore de sistema de arquivos extraída — não ambos. Executa o mesmo motor de deteção, regras personalizadas, allowlist e lógica de baseline usados pelo `scan`. Não requer root. O bundle é extraído para um diretório temporário privado que é eliminado assim que a análise termina (pode conter `/etc/shadow`).
A cobertura difere entre os dois modos offline, porque um bundle transporta estado capturado *reproduzido* do momento do `collect`, enquanto `--root` só tem o que estiver presente no sistema de arquivos montado:
- **`analyze BUNDLE`** reproduz a saída real dos comandos `ss`/`systemctl list-timers`/`journalctl` capturada no momento do `collect`, pelo que `NetworkCollector` e `SystemdCollector` produzem descobertas genuínas a partir desse snapshot — não são ignorados. A linha temporal é construída a partir do syslog/messages/audit.log/journalctl que o bundle capturou, pelo que está totalmente preenchida e as descobertas obtêm correlação real de `related_events`.
- **`analyze --root PATH`** aponta para uma imagem morta, montada, sem estado de processo ou kernel ativo para consultar, pelo que a execução de comandos é totalmente desativada: `NetworkCollector` e `SystemdCollector` são ignorados, registados em `scan_metadata.command_collectors_skipped`. A linha temporal é ainda construída, mas a partir dos ficheiros de log estáticos presentes na imagem (`/var/log/syslog`, `/var/log/messages`, `/var/log/audit/audit.log`) — a reprodução do journald não está disponível aqui, uma vez que não há um `journalctl` ativo para consultar uma imagem morta.
Consulte [Offline analysis: collect and analyze](#offline-analysis-collect-and-analyze) para a lista completa de lacunas de cobertura de deteção offline.
### Formato do bundle
Um bundle é um tarball comprimido com gzip com tudo sob um prefixo `bundle/`:```
bundle/
├── manifest.json
├── files/
│ ├── etc/passwd
│ ├── etc/shadow
│ ├── var/log/syslog
│ └── ... # every captured file, path-flattened under files/
└── commands/
├── ss.txt
├── netstat.txt
├── systemctl_list_timers_json.txt
├── systemctl_list_timers_text.txt
└── journalctl.txt
Esquema do manifest.json:
| Campo | Tipo | Descrição |
|---|---|---|
run_id | string | UUID gerado de novo para cada execução de collect |
host_id | string | Reservado para correlação multi-host futura; atualmente vazio |
hostname | string | socket.gethostname() no momento da coleta |
ubuntu_version | string | String da versão do Ubuntu detetada |
collected_at_utc_start / collected_at_utc_end | string (ISO 8601) | Limites de relógio de parede da execução de coleta |
tool_version | string | Versão do ubuntils que produziu o pacote |
files[] | array | Uma entrada por ficheiro capturado: source_path, bundle_path, sha256, size, mtime, ctime (um ficheiro ausente/ilegível no host de origem é registado com sha256: "", size: -1 em vez de abortar a coleta) |
commands[] | array | Uma entrada por comando capturado: name, argv, bundle_path, sha256, exit_code |
bundle_sha256 | string | SHA-256 sobre o resto do manifesto (tudo acima, serializado canonicamente) — a âncora de prova de adulteração para todo o pacote |
O scan_metadata.bundle_integrity do analyze reporta um de três valores:
"live" — scan e analyze --root reportam isto; não há pacote para verificar."ok" — analyze BUNDLE verificou o bundle_sha256 contra o manifesto e o SHA-256 de cada ficheiro capturado contra o seu conteúdo no pacote; nada foi alterado desde que collect o escreveu."mismatch" — o digest do manifesto, o hash de um ficheiro capturado ou o hash da saída de um comando capturado não corresponderam. Algo no pacote foi modificado, truncado ou corrompido após a coleta, e nada derivado dele deve ser considerado limpo em termos de cadeia de custódia. O analyze ainda produz o seu relatório, mas imprime um aviso vermelho para stderr, mostra uma faixa de integridade no topo do separador Summary da TUI e sai com o estado 3, para que os scripts não possam confundir os resultados de um pacote adulterado com resultados autoritativos.Uma execução de analyze proveniente de um pacote ou de --root não tem paridade de deteção com um scan em direto. Estes não são casos extremos; são limitações estruturais da aquisição estática e offline, e produzem menos (ou zero) resultados para as regras afetadas em vez de um erro. Quando um coletor sabe que não conseguiu ver (um comando falhado, um ficheiro ilegível), isso é registado em scan_metadata.collectors_degraded e sinalizado no separador Summary da TUI — mas um ficheiro que simplesmente não foi capturado parece igual a um ficheiro que não existe. (A linha temporal em si já não é uma dessas lacunas: analyze BUNDLE reproduz o syslog/messages/audit.log/journalctl capturado no momento do collect, e analyze --root lê os ficheiros de log estáticos presentes na imagem montada, pelo que ambos produzem uma linha temporal real e uma correlação real de related_events — ver ubuntils analyze acima.)
PROCESS_MASQUERADE e PROCESS_SUSPICIOUS_CONNECTION reportarão sempre zero resultados em modo offline. Ambas as regras dependem do campo exe de um processo, que é preenchido lendo o alvo da ligação simbólica /proc/<pid>/exe através da fonte de artefactos (nunca o /proc do próprio analista). Um pacote não tem um /proc em direto para ler, e --root aponta para uma árvore de sistema de ficheiros montada sem /proc também — atualmente não existe nenhum mecanismo para capturar ou reconstruir offline um alvo de ligação simbólica exe resolvido, pelo que exe está sempre vazio e ambas as regras nunca disparam, independentemente do que esteja realmente no host.collect não tem nenhum passo de captura por PID (/proc/*/status, /proc/*/cmdline), pelo que não existem processos num pacote para analisar em primeiro lugar — esta é a mesma causa raiz do ponto acima, do lado da aquisição.CRON_TMP_PATH, SUDOERS_NOPASSWD e SSH_UNAUTHORIZED_KEY estão limitados ou ausentes da análise proveniente de pacotes. A lista de ficheiros do collect é estática e não consegue expandir globs de /etc/cron.d/*, /etc/sudoers.d/*, /etc/profile.d/* ou ~/.ssh/authorized_keys por utilizador — apenas /etc/crontab, /etc/sudoers e /etc/environment//etc/profile são capturados. (--root contra uma árvore de sistema de ficheiros montada completa não tem esta lacuna, uma vez que os diretórios reais estão presentes no disco.) Quando SSH_UNAUTHORIZED_KEY ou (scan em direto, ou com os diretórios reais por utilizador presentes), agora também pontuam a confiança a partir do ctime e do conteúdo do ficheiro, não apenas do mtime — ver abaixo. Isso melhora o quanto deve confiar num resultado que dispara; não altera se a regra dispara offline em primeiro lugar.SUSPICIOUS_SYSTEMD_TIMER está enfraquecida para pacotes. As unidades de serviço são lidas diretamente dos diretórios de unidades (/etc/systemd/system, /usr/lib/systemd/system, ~/.config/systemd/user por utilizador, …), pelo que --root obtém cobertura total de serviços. Um pacote, no entanto, não captura esses diretórios: os timers aparecem a partir da saída capturada de systemctl list-timers, mas o ExecStart de cada timer vem de uma chamada systemctl show por unidade que o collect não faz, pelo que a regra não consegue avaliar o que um timer incluído no pacote executa.Quando é que isto importa: se está a fazer triagem de um host em direto e acessível, use sudo ubuntils scan — tem cobertura total de deteção. Use collect/analyze quando precisa de adquirir uma vez e analisar noutro local, precisa de analisar sem root, ou está a trabalhar a partir de uma imagem de disco onde scan não é de todo uma opção — e trate um resultado limpo de analyze para as regras acima como "não verificado", não como "verificado e limpo".
Executar sudo ubuntils scan (sem --json) lança uma TUI interativa de terminal completo.
Enquanto os coletores são executados, o ubuntils mostra uma lista de verificação em direto — uma linha por coletor. Cada linha é atualizada em tempo real à medida que o coletor termina:``` Scanning system…
✓ Process ✓ Network ✓ Users ⠹ Cron Systemd SSH Sudoers Environment
`✓` marca sucesso, `✗` marca falha, o spinner marca o coletor ativo e as linhas em branco estão pendentes. Quando todos os coletores terminam e a detecção + linha do tempo são concluídas, a TUI muda automaticamente para a tela de resultados.
### Tela de resultados
A tela de resultados tem quatro abas navegadas pelas teclas numéricas:
| Tecla | Aba | Conteúdo |
|-----|-----|----------|
| `1` | Resumo | Estatísticas da varredura + principais descobertas em um relance |
| `2` | Descobertas | Lista completa de descobertas com detalhes inline e remediação |
| `3` | Linha do tempo | Eventos de log correlacionados cronologicamente |
| `4` | Estatísticas | Versão do Ubuntu, arquitetura, duração, contagens de coletores |
Pressione `q` ou `Ctrl+C` para sair.
### Aba Resumo (tecla `1`)
Mostra metadados da varredura e as principais descobertas em uma única tela:```
Collectors: 8 run · 0 failed
Findings: 2 HIGH · 1 MEDIUM · 0 LOW
Timeline: 47 events
Duration: 2.8s
● HIGH CRON_TMP_PATH /etc/cron.d/cleanup
● HIGH LD_PRELOAD_INJECT /home/alice/.bashrc
○ MED SSH_UNAUTHORIZED_KEY /home/bob/.ssh/authorized_keys
Em um sistema limpo, esta aba mostra System appears clean.
2)Uma lista rolável de todos os achados, ordenada HIGH → MEDIUM → LOW. Selecionar um achado (Enter ou teclas de seta) expande um painel de detalhes na parte inferior, mostrando a descrição completa, o caminho do artefato, o valor bruto que acionou a detecção e informações de remediação.``` HIGH CRON_TMP_PATH /etc/cron.d/cleanup HIGH LD_PRELOAD_INJECT /home/alice/.bashrc MED SSH_UNAUTHORIZED_KEY /home/bob/.ssh/authorized_keys ─────────────────────────────────────────────────────── A cron job was found referencing /tmp, /var/tmp, or /dev/shm. These directories are world-writable and commonly used as attacker staging grounds.
Artifact: /etc/cron.d/cleanup Raw: 0 * * * * root /tmp/.update Fix: Will remove the offending cron entry from /etc/cron.d/cleanup after creating a timestamped backup.
R: remediate
### Remediação na TUI
Para achados que têm remediação automatizada disponível, pressione `R` enquanto o achado estiver selecionado. Um modal de confirmação aparece:```
┌─────────────────────────────────────────────────┐
│ Remediate CRON_TMP_PATH? │
│ │
│ Will remove the offending cron entry from │
│ /etc/cron.d/cleanup after creating a backup. │
│ Backup will be created at /var/backups/ubuntils/…│
│ │
│ Y: confirm Esc: cancel │
└─────────────────────────────────────────────────┘
Pressione Y para confirmar. O remediador é executado em uma thread em segundo plano. Quando ele é concluído, a linha da descoberta na lista é atualizada para [fixed] e o painel de detalhes mostra o resultado:```
✓ Remediated
Backup: /var/backups/ubuntils/20260115_142201/etc_cron.d_cleanup
Rollback: cp /var/backups/ubuntils/20260115_142201/etc_cron.d_cleanup /etc/cron.d/cleanup
Em caso de falha, o painel de detalhes mostra o erro. O backup é sempre criado antes de qualquer alteração ser tentada.
Pressione `Esc` para recolher o painel de detalhes.
### Aba Timeline (tecla `3`)
Uma lista cronológica rolável de eventos de log correlacionados. Cada linha mostra timestamp, origem e descrição. Os eventos são obtidos de syslog, journald e auditd e deduplicados.
### Aba Stats (tecla `4`)
Uma visão resumida da varredura: versão do Ubuntu detectada, arquitetura, duração da varredura, contagem de coletores e falhas, e contagem de achados por severidade, juntamente com o total de eventos da timeline.
---
## O que ele detecta
| Rule ID | Severity | Remediable | What it checks |
|---|---|---|---|
| CRON_ROOT_EXEC | HIGH | Yes | Crontabs de usuários não-root executando comandos em caminhos pertencentes ao root ou embutindo sudo |
| CRON_TMP_PATH | HIGH | Yes* | Qualquer tarefa cron (incluindo entradas `@reboot`/`@daily` e scripts em `/etc/cron.{hourly,daily,weekly,monthly}`) que referencie /tmp, /var/tmp ou /dev/shm. *Linhas de script são apenas sinalizadas |
| LD_PRELOAD_INJECT | HIGH | Yes | Qualquer entrada em /etc/ld.so.preload (vazio no Ubuntu padrão), ou LD_PRELOAD em qualquer arquivo de inicialização de shell com qualquer biblioteca listada fora de /lib, /usr/lib, /lib64, /usr/lib64 |
| SUSPICIOUS_SYSTEMD_TIMER | HIGH | No | Timers systemd e unidades de serviço cujo ExecStart referencia um diretório gravável por todos ou executa um binário não pertencente ao root |
| SSH_UNAUTHORIZED_KEY | MEDIUM | Yes | Arquivos authorized_keys modificados nos últimos 7 dias |
| USER_UID_ZERO | HIGH | No | Qualquer conta além de `root` com UID 0 (um segundo superusuário oculto) |
| USER_EMPTY_PASSWORD | HIGH | No | Uma conta com shell de login cujo campo de senha em /etc/shadow está vazio (o `nullok` padrão do PAM no Ubuntu permite login sem senha) |
| SUDOERS_NOPASSWD | MEDIUM | Yes* | Concessões NOPASSWD no sudoers para usuários com UID ≥ 1000 e shell de login, diretamente ou via uma regra `%group`; arquivos incluídos são seguidos. *Regras de grupo são apenas sinalizadas (remover `%sudo` poderia remover todo o acesso sudo) |
| PROCESS_MASQUERADE | MEDIUM | No | Processos cujo nome corresponde a um binário de sistema conhecido, mas cujo executável está fora dos diretórios padrão do sistema (/usr/bin, /usr/sbin, /bin, /sbin, /usr/local/{bin,sbin}, /usr/lib, /usr/libexec, /lib, /snap) |
| PROCESS_SUSPICIOUS_CONNECTION | HIGH / MEDIUM | No | Processos mantendo uma conexão de saída cujo executável está em um diretório temporário gravável por todos ou foi excluído do disco (HIGH), ou está fora dos diretórios padrão do sistema ou se comunica com uma porta remota não padrão (MEDIUM) |
| SHELL_RC_MODIFICATION | LOW | No | Arquivos de inicialização de shell (bashrc, profile, zshrc, etc.) modificados nas últimas 48 horas para qualquer usuário com shell de login |
| PACKAGE_TAMPERED | HIGH | No | Arquivos de pacotes pertencentes ao sistema modificados, ausentes, ou cujo conteúdo/modo/tamanho não corresponde ao manifesto do pacote (via `dpkg --verify`) |
| IMMUTABLE_FLAG_SET | MEDIUM | No | Flags imutável (`i`) ou somente-append (`a`) definidas em arquivos sensíveis como /etc/passwd, /etc/sudoers ou /etc/pam.d/* (detectado via `lsattr`) |
| PAM_BACKDOOR | HIGH | No | Uma linha literal `pam_permit.so` em qualquer arquivo /etc/pam.d/*, ou um módulo NSS em /etc/nsswitch.conf fora de uma allowlist (files/sss/ldap/winbind/...) |
| KERNEL_MODULE_SUSPICIOUS | LOW | No | Módulos de kernel carregados fora de uma allowlist de módulos internos comuns — nota: hosts com muito hardware (GPUs, placas Wi-Fi, drivers proprietários) verão falsos positivos; adicione módulos esperados via `--config` |
| SETUID_INVENTORY | LOW | No | Binários setuid ou setgid inesperados fora de um conjunto de baseline conhecido (os dois bits são verificados e reportados separadamente) |
### Por que cada regra existe
**CRON_ROOT_EXEC** — Crontabs de usuário são executados como o proprietário do crontab. Uma entrada que invoca sudo ou um interpretador pertencente ao root significa que o usuário arranjou para que código seja executado com privilégios de root de forma agendada, sem precisar de acesso sudo persistente. Isso sobrevive a mudanças de senha.
*Exemplo de achado:*```
[HIGH] CRON_ROOT_EXEC
Title: User crontab executing with sudo
Artifact: /var/spool/cron/crontabs/alice
Raw value: */5 * * * * sudo /usr/bin/python3 /tmp/beacon.py
Remediation: available
CRON_TMP_PATH — Diretórios com permissão de escrita global, como /tmp e /dev/shm, são áreas de preparação padrão para atacantes. Um cron job apontando para lá significa que um payload pode ser trocado entre invocações sem tocar em nenhum caminho persistente. Isso abrange entradas no estilo @reboot/@daily e os scripts em /etc/cron.{hourly,daily,weekly,monthly}; achados nesses scripts são apenas sinalizados, já que excluir uma linha de um shell script não é uma correção automática segura.
Exemplo de achado:``` [HIGH] CRON_TMP_PATH Title: Cron job references writable temp directory Artifact: /etc/cron.d/cleanup Raw value: 0 * * * * root /tmp/.update Remediation: available
**LD_PRELOAD_INJECT** — LD_PRELOAD faz com que o linker dinâmico carregue uma biblioteca compartilhada especificada antes de todas as outras, permitindo a interceptação arbitrária de funções em qualquer binário com ligação dinâmica. Um valor apontando para fora dos caminhos padrão de bibliotecas é um indicador quase certo de rootkit em espaço de usuário; cada biblioteca em uma lista separada por espaço ou dois-pontos é verificada. `/etc/ld.so.preload` injeta em *todos* os processos e está vazio em uma instalação padrão do Ubuntu, portanto qualquer entrada ali é reportada — mesmo uma plantada dentro de `/lib`, um truque comum de rootkit. A remediação remove as entradas de `/etc/ld.so.preload` em vez de comentá-las, porque o loader não possui sintaxe de comentário nesse arquivo.
*Exemplo de achado:*```
[HIGH] LD_PRELOAD_INJECT
Title: LD_PRELOAD set to non-standard library path
Artifact: /home/alice/.bashrc
Raw value: export LD_PRELOAD=/tmp/.libssl.so
Remediation: available
SUSPICIOUS_SYSTEMD_TIMER — Os timers do systemd são mais persistentes e menos visíveis do que os cron jobs para a maioria dos respondedores. Um timer — ou uma unit .service simples, que é a persistência mais comum e não precisa de timer algum — cujo ExecStart faz referência a um diretório temporário ou executa um binário que não pertence ao root é um sinal de persistência criada pelo atacante. As units de serviço são lidas diretamente dos diretórios de units, incluindo o ~/.config/systemd/user por usuário. Apenas sinalização — a remoção de units do systemd exige julgamento humano.
Exemplo de achado:``` [HIGH] SUSPICIOUS_SYSTEMD_TIMER Title: Systemd timer ExecStart points to suspicious path Artifact: /etc/systemd/system/update-check.timer Raw value: ExecStart=/tmp/.sys/update Remediation: not available
**SSH_UNAUTHORIZED_KEY** — Uma chave SSH recém-adicionada concede acesso remoto persistente independentemente de senhas. A janela de 7 dias captura adições recentes enquanto evita ruído do provisionamento inicial em sistemas mais antigos. Nota: a regra usa o mtime do arquivo, que reflete a última escrita no arquivo authorized_keys, não o timestamp de inserção de cada chave individual.
*Exemplo de descoberta:*```
[MEDIUM] SSH_UNAUTHORIZED_KEY
Title: SSH authorized key added in last 7 days
Artifact: /home/bob/.ssh/authorized_keys
Raw value: ssh-rsa AAAAB3NzaC1... attacker@evil
Remediation: available
SUDOERS_NOPASSWD — sudo sem senha para uma conta de usuário humano (UID ≥ 1000 com um shell de login) é um vetor de escalonamento de privilégios que sobrevive à remoção de outros mecanismos de persistência. Concessões legítimas de NOPASSWD são quase sempre para contas de serviço sem shell de login. Regras de grupo (%sudo ALL=(ALL) NOPASSWD:ALL) são resolvidas para os seus membros, e arquivos #include/@includedir são seguidos. Achados de grupo são apenas sinalizados: excluir uma regra como %sudo poderia remover todas as concessões de sudo no sistema.
Exemplo de achado:``` [MEDIUM] SUDOERS_NOPASSWD Title: NOPASSWD sudo grant for regular user Artifact: /etc/sudoers.d/alice Raw value: alice ALL=(ALL) NOPASSWD: ALL Remediation: available
**PROCESS_MASQUERADE** — Nomear um binário malicioso com o nome de um processo de sistema conhecido (sshd, python3, bash) é uma técnica básica para evitar deteção na saída de `ps`. Esta regra faz referência cruzada entre o nome do processo de `/proc/<pid>/status` e o caminho do exe resolvido a partir de `/proc/<pid>/exe`. As localizações padrão incluem `/usr/local/{bin,sbin}`, `/usr/lib`, `/usr/libexec` e `/snap`, pelo que o systemd (`/usr/lib/systemd/systemd`) e os pacotes snap não a acionam. Apenas sinalização — matar um processo requer julgamento humano.
*Exemplo de deteção:*```
[MEDIUM] PROCESS_MASQUERADE
Title: Process masquerading as system binary
Artifact: /proc/1337/exe
Raw value: name=sshd, exe=/tmp/.sshd
Remediation: not available
USER_UID_ZERO — Apenas root deve possuir UID 0. Uma segunda conta mapeada para UID 0 (CIS Ubuntu Benchmark 6.2.x) é um backdoor de alta confiança: concede direitos totais de superusuário sem alterar as próprias credenciais do root e sobrevive a uma redefinição de senha do root. Taxa de falsos positivos próxima de zero. Apenas sinalização — remover uma conta com UID 0 exige julgamento humano.
Exemplo de achado:``` [HIGH] USER_UID_ZERO Title: Non-root account with UID 0 Artifact: /etc/passwd Raw value: toor❌0:0:...:/bin/bash Remediation: not available
**USER_EMPTY_PASSWORD** — Uma conta cujo campo de senha em `/etc/shadow` está vazio não tem senha alguma, e a pilha PAM padrão do Ubuntu (`pam_unix ... nullok`) permite que ela faça login sem senha. Em uma conta com shell de login, isso é uma porta aberta. Apenas sinalização — bloqueie-a com `passwd -l` enquanto investiga.
*Exemplo de achado:*```
[HIGH] USER_EMPTY_PASSWORD
Title: Login account with no password
Artifact: /etc/shadow
Raw value: eve::
Remediation: not available
PROCESS_SUSPICIOUS_CONNECTION — A persistência é apenas metade do quadro; um ponto de apoio que nunca comunica com nada raramente é aquele que lhe interessa. Esta regra junta os coletores de processo e de rede por PID, para que um processo sinalizado chegue com as suas ligações atuais anexadas. Um executável colocado em /tmp com um socket de saída estabelecido é HIGH; um binário legítimo a alcançar uma porta remota não padrão é MEDIUM e merece uma análise. Isto é um instantâneo do estado atual, não monitorização contínua — um beacon que esteja a dormir quando faz a verificação não aparecerá. Apenas sinalização.
Exemplo de deteção:``` [HIGH] PROCESS_SUSPICIOUS_CONNECTION Title: Process with suspicious outbound connection Artifact: /proc/1337/exe Raw value: 203.0.113.9:4444 Remediation: not available
**SHELL_RC_MODIFICATION** — Arquivos de inicialização do shell são um vetor de persistência confiável porque são executados a cada login do usuário. Esta regra expõe modificações recentes para revisão humana. Apenas sinalização — o conteúdo do RC do shell requer leitura antes de agir sobre ele.
*Exemplo de descoberta:*```
[LOW] SHELL_RC_MODIFICATION
Title: Shell init file recently modified
Artifact: /root/.bashrc
Raw value: mtime=2024-01-15 14:22:01 (6 hours ago)
Remediation: not available
PACKAGE_TAMPERED — Binários e arquivos de configuração pertencentes ao sistema são a base da confiança. Esta regra detecta quando arquivos pertencentes a pacotes foram modificados, excluídos ou apresentam divergências de conteúdo/modo/tamanho usando dpkg --verify. Edições apenas em conffiles (alterações locais de configuração esperadas) são excluídas do relatório para evitar ruído. Apenas sinalização — a adulteração pode ser legítima (edições locais personalizadas) ou maliciosa (substituição de arquivo); a decisão exige julgamento humano.
Exemplo de achado:``` [HIGH] PACKAGE_TAMPERED Title: Package-owned file modified since installation Artifact: /usr/bin/sshd Raw value: ....5..T. (content and mtime differ) Remediation: not available
**IMMUTABLE_FLAG_SET** — Os atacantes frequentemente definem a flag immutable (`i`) ou a flag append-only (`a`) em ficheiros para impedir a modificação ou eliminação, mesmo por root, incluindo para ocultar adulterações de edições posteriores/rotação de logs. Definir estas flags em ficheiros de sistema sensíveis como /etc/passwd, /etc/sudoers, /etc/pam.d/*, ou os ficheiros de log auth/syslog/wtmp/btmp é um forte indicador de endurecimento por parte do atacante. Esta regra deteta flags immutable e append-only via `lsattr` contra essa lista fixa de caminhos sensíveis. Apenas sinalização — alterações de flags requerem revisão humana.
*Exemplo de deteção:*```
[MEDIUM] IMMUTABLE_FLAG_SET
Title: Sensitive file has an unexpected chattr flag
Artifact: /etc/ld.so.preload
Raw value: ----i--------e---
Remediation: not available
PAM_BACKDOOR — PAM (Pluggable Authentication Modules) e NSS (Name Service Switch) são os sistemas centrais de autenticação e identidade no Linux. Esta regra NÃO faz deteção baseada em mtime do tipo "este ficheiro foi modificado" — faz correspondência de padrões no conteúdo do ficheiro: (1) uma linha literal pam_permit.so em qualquer ficheiro /etc/pam.d/* (este módulo tem sempre sucesso e é um backdoor clássico de contorno de autenticação), ou (2) um módulo NSS listado em /etc/nsswitch.conf que não esteja na lista de permissões incorporada do ubuntils. Nota: a verificação NSS dará falsos positivos em hosts associados a domínio/SSSD/LDAP/Winbind que usem um módulo fora da lista de permissões incorporada — é intencionalmente pontuada com menor confiança do que a correspondência pam_permit.so por esta razão; use --config para permitir nomes de módulos não reconhecidos no seu ambiente. Apenas sinalização — alterações à configuração de autenticação exigem verificação cuidadosa.
Exemplos de resultados:``` [HIGH] PAM_BACKDOOR Title: PAM config unconditionally permits authentication Artifact: /etc/pam.d/sshd Raw value: auth required pam_permit.so Remediation: not available
[HIGH] PAM_BACKDOOR Title: Unexpected NSS module in nsswitch.conf Artifact: /etc/nsswitch.conf Raw value: passwd: files evilmod Remediation: not available
**KERNEL_MODULE_SUSPICIOUS** — Módulos de kernel são executados em ring 0 com acesso irrestrito. Atacantes frequentemente carregam módulos de kernel personalizados para rootkits, sniffing de pacotes ou ocultação de processos. Esta regra compara os módulos atualmente carregados com uma pequena allowlist de módulos internos esperados (comuns à maioria dos sistemas). É de severidade **LOW** porque essa allowlist é deliberadamente restrita. **Nota:** hosts com muito hardware, como drivers de GPU, placas Wi-Fi ou drivers proprietários, gerarão falsos positivos. Os respondedores devem adicionar os módulos esperados do seu host via `--config`, permitindo por nome de módulo (usado como `artifact_path`). Apenas sinalização — a investigação de módulos de kernel exige ferramentas forenses e conhecimento humano.
*Exemplo de achado:*```
[LOW] KERNEL_MODULE_SUSPICIOUS
Title: Loaded kernel module not in the expected set
Artifact: implant_rootkit
Raw value: {'name': 'implant_rootkit', 'size': '12288', 'used_by': []}
Remediation: not available
SETUID_INVENTORY — Binários setuid e setgid escalam privilégios automaticamente quando executados. Atacantes criam binários setuid/setgid personalizados para persistir a escalada de privilégios, na maioria das vezes fora dos diretórios padrão de binários do sistema (um caminho de instalação convencional como /opt, o próprio /home de um usuário, /srv, ou um diretório temporário com permissão de escrita global). Esta regra inventaria binários setuid/setgid via find -perm -4000 -o -perm -2000 em /usr /bin /sbin /opt /home /srv /tmp /var/tmp /dev/shm e sinaliza qualquer um fora de um conjunto de linha de base conhecido de utilitários legítimos do sistema. Apenas sinalização — binários setuid/setgid inesperados exigem investigação, mas podem ser binários legítimos instalados por aplicações.
Exemplo de deteção:``` [LOW] SETUID_INVENTORY Title: Unexpected setuid binary Artifact: /tmp/.hidden/backdoor Raw value: setuid Remediation: not available
---
## Saída JSON
`--json` grava um único objeto JSON em stdout. Nada mais é impresso.```json
{
"scan_metadata": {
"tool_version": "2.1.0",
"hostname": "web-01",
"generated_at": "2026-06-10T08:22:03.114523+00:00",
"ubuntu_version": "Ubuntu 22.04.3 LTS",
"architecture": "x86_64",
"duration_s": 2.84,
"collector_failures": 0,
"bundle_integrity": "live",
"command_collectors_skipped": [],
"collectors_degraded": {},
"rules_failed": [],
"timeline_error": null,
"suppressed_by_baseline": 1
},
"artifact_counts": {
"ProcessCollector": 142,
"NetworkCollector": 23,
"UserCollector": 4,
"CronCollector": 7,
"SystemdCollector": 12,
"SSHCollector": 3,
"SudoersCollector": 5,
"EnvironmentCollector": 18
},
"findings": [
{
"rule_id": "CRON_TMP_PATH",
"severity": "HIGH",
"title": "Cron job references writable temp directory",
"description": "A cron job was found referencing /tmp, /var/tmp, or /dev/shm. These directories are world-writable and commonly used as attacker staging grounds.",
"artifact_path": "/etc/cron.d/cleanup",
"raw_value": "0 * * * * root /tmp/.update",
"remediation_available": true,
"remediation_description": "Will remove the offending cron entry from /etc/cron.d/cleanup after creating a timestamped backup.",
"related_events": [
{
"timestamp": "2024-01-15T08:20:00+00:00",
"source": "syslog",
"description": "CRON[2841]: (root) CMD (/tmp/.update)"
}
],
"confidence": 75,
"confidence_band": "HIGH",
"signals": [
{"name": "timeline_corroboration", "weight": 25, "detail": "1 nearby timeline event(s)"}
]
},
{
"rule_id": "PROCESS_MASQUERADE",
"severity": "MEDIUM",
"title": "Process masquerading as system binary",
"description": "Process 'sshd' (pid=1337) has exe path outside standard binary directories: /tmp/.sshd",
"artifact_path": "/proc/1337/exe",
"raw_value": "/tmp/.sshd",
"remediation_available": false,
"guided_remediation": "Confirm pid 1337 is malicious (`ls -l /proc/1337/exe`, `cat /proc/1337/cmdline`), then terminate it: `kill -9 1337`.",
"confidence": 50,
"confidence_band": "MEDIUM",
"signals": []
}
],
"timeline": [
{
"timestamp": "2024-01-15T08:22:01+00:00",
"source": "syslog",
"description": "sshd: Accepted publickey for alice from 10.0.0.42 port 52341"
}
],
"report_sha256": "a3f1c9…(64 hex chars)"
}
remediation_results aparece como uma chave adicional de nível superior apenas quando --remediate é passado. report_sha256 está sempre presente e é calculado sobre o resto do documento. scan_metadata.bundle_integrity é "live" para scan e analyze --root, "ok" para um bundle verificado passado para analyze, e "mismatch" se o conteúdo de um bundle não corresponder ao seu manifesto — consulte Integridade do bundle na saída JSON. scan_metadata.command_collectors_skipped nomeia quaisquer coletores baseados em comandos (NetworkCollector, SystemdCollector, PackageCollector, KernelCollector) ignorados numa execução --root — sempre vazio para scan e analyze BUNDLE. scan_metadata.suppressed_by_baseline é a contagem de achados que um arquivo --baseline removeu deste relatório — consulte Baseline de estado conhecido como bom. scan_metadata.collectors_degraded mapeia o nome de um coletor para os motivos pelos quais os seus dados estão incompletos (um comando que falhou ou expirou, um arquivo ilegível, uma linha malformada ignorada), rules_failed lista qualquer regra de detecção que falhou, e timeline_error é definido se a linha do tempo não pôde ser construída. Todos os três estão vazios numa execução saudável. Verifique-os antes de ler uma lista de achados vazia como "limpa".
related_events e guided_remediation aparecem num achado apenas quando têm conteúdo. related_events contém até cinco eventos da linha do tempo correspondidos ao achado por caminho de artefato e palavras-chave da regra, mais recentes primeiro — um auxílio de superfície, não uma afirmação causal. guided_remediation é uma sequência de comandos revisada para você executar manualmente; o ubuntils nunca a executa.
Cada achado carrega uma pontuação confidence (0–100, padrão 50) e uma confidence_band (HIGH ≥ 75, MEDIUM ≥ 40, LOW abaixo disso), além de uma lista signals mostrando exatamente como essa pontuação foi alcançada — cada entrada é {"name", "weight", "detail"}, então a pontuação é sempre explicável, nunca uma caixa preta. Os sinais são aditivos sobre uma confiança base de 50 e são aplicados pela regra ou estágio do pipeline que os produziu:
SSH_UNAUTHORIZED_KEY e SHELL_RC_MODIFICATION adicionam content_match (+30) quando o conteúdo do artefato corresponde a um padrão conhecido como perigoso (uma opção de chave SSH perigosa; uma linha curl/wget-to-shell ou base64-decode num arquivo RC de shell), ctime_corroborates_mtime (+20) quando o ctime do arquivo também está dentro da janela de detecção (mais difícil de forjar do que apenas o mtime), ou mtime_only (−20) quando a recenticidade é o único sinal e o ctime não o corrobora — uma indicação de que o mtime pode ter sido retrodatação.timeline_corroboration (+25) após a correlação achado↔linha do tempo, quando um achado tem um ou mais related_events.Um achado na faixa LOW não é descartado nem ocultado — ele ainda aparece na lista de achados e na saída JSON exatamente como qualquer outro — mas a faixa indica quanto peso dar a ele antes de investigar mais. Isso substitui a antiga heurística baseada apenas em mtime para SSH_UNAUTHORIZED_KEY/SHELL_RC_MODIFICATION, onde um arquivo obsoleto mas legitimamente tocado (por exemplo, uma ferramenta de gerenciamento de configuração reescrevendo .bashrc a cada execução) parecia idêntico a um backdoor genuinamente novo.
Limitação conhecida: apenas sinais de padrão de conteúdo e ctime estão ativos hoje. Sinais baseados em propriedade/fingerprint — uma fingerprint de chave SSH desconhecida, a opção de restrição from= de uma chave, ou uma incompatibilidade de proprietário/modo de arquivo RC (um sinal ownership_anomaly) — ainda não estão implementados. Esta é uma lacuna de cobertura adiada, rastreada para uma versão futura, não algo que a pontuação de confiança atual contabilize.
Cinco das dezesseis regras de detecção têm remediação automatizada: CRON_ROOT_EXEC, CRON_TMP_PATH, LD_PRELOAD_INJECT, SSH_UNAUTHORIZED_KEY e SUDOERS_NOPASSWD. As restantes são apenas de sinalização e nunca serão remediadas automaticamente, porque agir sobre elas com segurança exige que um humano olhe primeiro.
SUSPICIOUS_SYSTEMD_TIMER, PROCESS_MASQUERADE e SHELL_RC_MODIFICATION carregam uma string guided_remediation: os comandos exatos a executar depois de confirmar o achado — systemctl disable --now <unit>, kill -9 <pid>, ou a revisão e reversão do arquivo RC. Ela aparece no painel de detalhes da TUI e no JSON. O ubuntils nunca a executa por você; estas regras ficam fora da varredura --remediate --confirm por design.
Selecione qualquer achado com uma remediação na aba Findings, depois pressione R. Um modal de confirmação pré-visualiza a ação planejada. Pressione Y para aplicá-la — o remediador é executado numa thread em segundo plano para que a TUI permaneça responsiva. A linha do achado é atualizada para [fixed] quando concluído, com o caminho de backup e o comando de rollback exato mostrados inline.
--remediate sem --confirm é uma simulação segura: backups são criados e a validação é executada, mas nenhuma alteração é aplicada. Passe ambos os flags para realmente fazer alterações. O pipeline é executado antes do lançamento da TUI neste modo, e a aba Summary lista cada resultado de remediação com o seu caminho de backup e comando de rollback.
--remediate --confirm só age sobre achados com uma pontuação de confiança de pelo menos 40 (a faixa MEDIUM) — um SSH_UNAUTHORIZED_KEY de baixa confiança, apenas com mtime, é reportado como SKIPPED em vez de ter a sua chave excluída. Ajuste o limite com --min-confidence N.```bash
sudo ubuntils scan --remediate # dry run
sudo ubuntils scan --remediate --confirm # apply changes, then open TUI
sudo ubuntils scan --remediate --confirm --min-confidence 75 # only HIGH-confidence findings
### Salvaguardas
Toda remediação segue o mesmo padrão, independentemente de como é acionada:
1. Detectar se o caminho do artefato é um symlink — recusar se for (impede que o root escreva através de symlinks controlados pelo atacante)
2. Criar um backup com timestamp em `/var/backups/ubuntils/YYYYMMDD_HHMMSS/` com modo `0700`
3. Validar o estado atual (a linha exata ainda deve estar presente)
4. Aplicar a mudança mínima possível — entradas de cron e chaves removidas linha a linha; linhas LD_PRELOAD em arquivos de inicialização de shell comentadas; entradas em `/etc/ld.so.preload` removidas (o loader não tem sintaxe de comentário lá, então uma entrada comentada ainda seria carregada). Para sudoers, o conteúdo editado é verificado com `visudo -cf` em uma cópia temporária *antes* de o arquivo real ser tocado
5. Escrever atomicamente — o novo conteúdo vai para um arquivo temporário ao lado do original (mesmo modo e proprietário), é fsync'd, e então renomeado sobre ele, para que uma falha no meio da escrita não possa deixar um `/etc/sudoers` truncado
6. Verificar que a linha exata desapareceu
Se qualquer etapa falhar, a remediação para imediatamente, o sistema é deixado inalterado, e o erro completo é reportado com o caminho do backup e o comando de rollback. O acesso sudo é protegido de duas formas: regras NOPASSWD de `%group` (como `%sudo`) são apenas sinalizadas e nunca removidas automaticamente, e o remediador de sudoers se recusa a remover a última regra do arquivo sudoers principal.
---
## Integração com Wazuh
ubuntils é construído para triagem de host único e pontual — você o executa quando
já suspeita que algo está errado, e ele nunca se comunica com o exterior nem continua
observando após o término da varredura. Isso é deliberado, mas também significa que um achado
do ubuntils vive apenas naquele relatório, a menos que algo o carregue
adiante. A maioria das equipes que executa Ubuntu em qualquer escala já tem um SIEM fazendo
o lado contínuo da detecção, então em vez de construir o ubuntils como seu próprio
agente de monitoramento de longa duração, ele entrega seus achados ao que você provavelmente
já executa: Wazuh.
Se um agente Wazuh estiver presente no host (`/var/ossec/bin/wazuh-agentd` ou
`/var/ossec/etc/ossec.conf` existir), `ubuntils scan` (a menos que executado com `--no-wazuh`) anexa
cada achado como uma linha JSON a `/var/log/ubuntils/wazuh-alerts.json` para
o agente capturar — este é um encaminhador puro, não um módulo Wazuh: nenhuma
chamada de rede, nenhuma chave de API, nada além das mesmas escritas de artefatos locais que o
ubuntils já faz — embora o agente, é claro, enviará essas linhas
para fora do host ao seu manager; esse é o ponto. É auto-detectado sem
flag necessária (use `--no-wazuh` para optar por não executar), então um
`ubuntils scan` agendado ou por script em uma frota de hosts inscritos no agente
começa a alimentar o SIEM imediatamente sem fiação extra. Isso nunca
acontece durante o `ubuntils analyze` offline (bundle ou `--root`), já que
esses achados descrevem um host diferente do que executa o agente
Wazuh local — encaminhar os achados de um bundle para o agente do próprio analista
os atribuiria erroneamente à máquina errada.
A intenção é integrar o ubuntils a um pipeline existente de alertas/escalonamento
em vez de pedir a um respondedor que cuide de uma segunda ferramenta: uma vez que as
regras de exemplo abaixo estejam carregadas, um achado de severidade HIGH do ubuntils (uma nova
conta UID-0, um rootkit LD_PRELOAD, um backdoor PAM) aparece como um alerta
normal do Wazuh, herda qualquer roteamento de notificação que o manager já
tenha configurado, e fica ao lado de todos os outros sinais na mesma
linha do tempo, em vez de um arquivo JSON isolado que alguém precisa lembrar de
verificar.
Para que o Wazuh analise e alerte sobre esses achados, copie as regras de exemplo
de `examples/wazuh/` para o seu manager Wazuh, e adicione o bloco `<localfile>`
de `examples/wazuh/ossec_localfile_snippet.xml` ao
`/var/ossec/etc/ossec.conf` do agente. Nenhuma instalação de decoder personalizado é necessária: o
localfile é configurado com `log_format json`, então o decoder JSON embutido do Wazuh
analisa cada linha e mapeia cada chave JSON de nível superior `k` para `data.k`,
que o `local_rules.xml` corresponde diretamente.
1. `examples/wazuh/local_rules.xml` → `/var/ossec/etc/rules/` do manager
2. O bloco `<localfile>` de `examples/wazuh/ossec_localfile_snippet.xml` → `/var/ossec/etc/ossec.conf` do agente
3. Reinicie ambos: `systemctl restart wazuh-manager` (manager), `systemctl restart wazuh-agent` (host do agente)
Estes são apenas templates de exemplo, fornecidos como ponto de partida — eles não
foram testados contra um manager Wazuh em produção e devem ser verificados em um
ambiente de não produção antes de confiar neles.
**Esquema JSON por linha:**
| Campo | Tipo | Descrição |
|---|---|---|
| `timestamp` | string (ISO 8601) | Quando o achado foi encaminhado |
| `hostname` | string | O host escaneado |
| `rule_id` | string | Corresponde ao ID da regra de detecção do ubuntils (veja a tabela Detection Rules acima) |
| `severity` | string | `HIGH` \| `MEDIUM` \| `LOW` |
| `title` | string | Título curto legível por humanos |
| `description` | string | Descrição completa do achado |
| `artifact_path` | string | Caminho do arquivo/recurso onde o problema foi encontrado |
| `raw_value` | string | A linha/valor bruto que acionou a regra |
| `remediation_available` | bool | Se o ubuntils tem um remediador para esta regra |
| `related_events` | array (opcional) | Eventos de linha do tempo correlacionados, se houver |
---
## Coletores
| Coletor | Artefatos coletados |
|---|---|
| ProcessCollector | Processos em execução de `/proc` e saída de `ps` |
| NetworkCollector | Conexões abertas e listeners de `ss`/`netstat` |
| UserCollector | `/etc/passwd`, `/etc/shadow`, `/etc/group` |
| CronCollector | Diretórios `/etc/cron*` e `/var/spool/cron/crontabs/*` |
| SystemdCollector | Saída de `systemctl list-timers` e `list-units` |
| SSHCollector | `~/.ssh/authorized_keys` para todos os usuários |
| SudoersCollector | `/etc/sudoers` e todos os arquivos sob `/etc/sudoers.d/` |
| EnvironmentCollector | `/etc/environment`, `/etc/profile.d/*`, arquivos de inicialização de shell do usuário |
| PackageCollector | Integridade de pacotes do sistema via `dpkg --verify`, atributos de flag imutável via `lsattr`, e binários setuid/setgid via `find` |
| PamCollector | Arquivos `/etc/pam.d/*` e `/etc/nsswitch.conf` |
| KernelCollector | Módulos de kernel carregados via `lsmod` |
### Dependências dos coletores
`PackageCollector` requer três ferramentas padrão do Ubuntu no host ativo (`dpkg`, `lsattr`, `find` — todas presentes em instalações padrão do Ubuntu). Se qualquer comando estiver indisponível, `PackageCollector` produz graciosamente dados vazios para aquela porção em vez de travar. A análise offline (`analyze BUNDLE`) reproduz a saída de comando capturada no momento do `collect`, então a disponibilidade de comandos no host do analisador não é necessária.
A varredura `find` de setuid/setgid passa `-xdev` para permanecer limitada — ela não desce em sistemas de arquivos montados separadamente (uma montagem distinta sob `/opt`, um `/home` montado via NFS, etc.). Esta é uma troca deliberada entre tempo de execução e cobertura: sem `-xdev`, a varredura poderia travar escaneando montagens de rede ou sistemas de arquivos virtuais. Se o seu ambiente monta esses caminhos em sistemas de arquivos separados, esteja ciente de que eles não serão escaneados.
`dpkg --verify` e a varredura `find` de setuid/setgid usam timeouts generosos não padrão (10 minutos e 5 minutos respectivamente, veja `ubuntils/collectors/packages.py`) já que ambos podem legitimamente executar bem além do padrão da biblioteca de 30 segundos em um host real com um grande banco de dados de pacotes ou árvore de sistema de arquivos; `ubuntils collect` usa os mesmos timeouts ao capturar esses comandos em um bundle.
---
## Compatibilidade
| | Suportado |
|---|---|
| **Ubuntu** | 20.04, 22.04, 24.04 |
| **Arquitetura** | amd64, arm64 |
| **Python** | 3.9+ |
| **Privilégios** | Root necessário para acesso completo aos artefatos |
Executar sem root produz uma varredura parcial com avisos. Caminhos críticos como `/etc/shadow`, diretórios de crontab protegidos, e algumas entradas de `/proc` serão ignorados.
---
## Roadmap
**v1.0.0**
- [x] Todos os 8 coletores
- [x] Todas as 8 regras de detecção
- [x] Construtor de linha do tempo (syslog, journald, auditd)
- [x] Tela de progresso de varredura ao vivo com ✓/✗ por coletor
- [x] TUI interativa de quatro abas (Summary / Findings / Timeline / Stats)
- [x] Remediação na TUI com modal de confirmação e worker em segundo plano
- [x] Modo de saída JSON
- [x] Remediação via CLI para 5 regras com backup, rollback e proteção contra symlink
- [x] Suporte a Ubuntu 20.04/22.04/24.04
- [x] 240 testes com 90% de cobertura
**v1.1.0**
- [x] Allowlisting de falsos positivos por id de regra ou caminho (`--config`)
- [x] `--output FILE` para escrever relatórios diretamente
- [x] Janela de linha do tempo com `--since`
- [x] Relatórios à prova de adulteração (`report_sha256`, hostname, timestamp)
- [x] Regra de detecção `USER_UID_ZERO`
**v1.5.0**
- [x] Regras de detecção personalizadas por correspondência de padrão via YAML (`--rules`)
- [x] `PROCESS_SUSPICIOUS_CONNECTION` — correlação processo↔rede por PID
- [x] Correlação automática achado↔linha do tempo (`related_events`)
- [x] Remediação guiada para as três regras que exigem julgamento
- [x] 282 testes com 92% de cobertura
Consultas de hash no VirusTotal e exportação de IOC para MISP foram removidas desta versão. O VirusTotal só responde para hashes *conhecidos* — o caso que o `rkhunter` já cobre, e o oposto da lacuna de técnicas novas que o ubuntils visa — e ambos os recursos teriam colocado uma chamada de rede dentro de uma ferramenta cujo valor repousa em não fazer nenhuma. O próprio ubuntils ainda não faz chamadas de rede (veja a [integração com Wazuh](#wazuh-integration) para a única forma opt-out de achados saírem do host, via um agente local).
**v2.0.0 — divisão offline collect/analyze**
- [x] `ubuntils collect` — adquire um bundle à prova de adulteração (`manifest.json` + arquivos/comandos com hash) de um host ativo, sem detecção
- [x] `ubuntils analyze (BUNDLE | --root PATH)` — executa o mesmo pipeline de detecção/linha do tempo que o `scan` contra um bundle ou uma imagem montada, sem necessidade de root
- [x] `bundle_integrity` (`live`/`ok`/`mismatch`) exposto em `scan_metadata`
- [x] Lacunas de cobertura de detecção documentadas no modo offline (`PROCESS_MASQUERADE`, `PROCESS_SUSPICIOUS_CONNECTION`, e cobertura reduzida para caminhos glob de cron/sudoers/SSH e `ExecStart` de timer systemd)
- [x] Detecção confiável: pontuação de confiança (`confidence`/`confidence_band`/`signals`), supressão via `--baseline`, substituindo heurísticas apenas de mtime em `SSH_UNAUTHORIZED_KEY`/`SHELL_RC_MODIFICATION` por sinais de ctime + conteúdo, e uma linha do tempo offline real tanto para `analyze BUNDLE` quanto para `analyze --root`
- [x] Pacote de cobertura: `PACKAGE_TAMPERED`, `IMMUTABLE_FLAG_SET`, `PAM_BACKDOOR`, `KERNEL_MODULE_SUSPICIOUS`, `SETUID_INVENTORY` (via `PackageCollector`, `PamCollector`, `KernelCollector`; todos apenas sinalizados por design)
- [x] 400 testes com 93,79% de cobertura
**v2.1.0 — encaminhamento para SIEM**
- [x] Achados de `ubuntils scan` ao vivo encaminhados para um agente Wazuh local como JSONL, auto-detectado (sem flag necessária)
- [x] Decoder/regras de exemplo do Wazuh e snippet `<localfile>` do `ossec.conf` (`examples/wazuh/`)
- [x] Encaminhamento intencionalmente limitado apenas ao `scan` ao vivo — nunca dispara durante `analyze` offline, já que um bundle ou imagem descreve um host diferente do que executa o agente
- [x] `--no-wazuh` para optar por não encaminhar em uma varredura
**v2.1.0 — hardening (auditoria completa do código-base)**
- [x] Segurança: o re-exec do sudo não encaminha mais o `PATH` do chamador, e os comandos resolvem em um caminho seguro fixo; edições de sudoers são verificadas com `visudo` *antes* de tocar no arquivo; escritas de remediação atômicas; saída de relatório/bundle segura contra symlink; bundles extraídos excluídos após `analyze`
- [x] Cobertura de detecção: `/etc/ld.so.preload` analisado, entradas de cron `@reboot`/`@daily` e scripts de `/etc/cron.{hourly,daily,weekly,monthly}`, unidades `.service` do systemd e binários ExecStart não pertencentes ao root, regras sudoers de `%group` e `#include`/`@includedir`, cada elemento de uma lista `LD_PRELOAD`
- [x] Nova regra `USER_EMPTY_PASSWORD` (conta de login sem senha)
- [x] Menos falsos positivos: verificações de caminho delimitadas por separador, setuid vs setgid diferenciados, daemons snap/`/usr/lib` tratados como padrão, `KERNEL_MODULE_SUSPICIOUS` rebaixado para LOW, um achado NSS por módulo
- [x] Relatórios honestos: regras falhas e coletores degradados registrados em `scan_metadata` e na TUI; uma falha de linha do tempo não descarta mais achados; bundles adulterados avisam e saem com código 3; ano/fuso horário do syslog tratados corretamente
- [x] Remediação mais segura: gate `--min-confidence` (padrão 40), resultados mostrados na TUI após `scan --remediate`
- [x] 444 testes com 94,29% de cobertura
**v3.0.0 / v4.0.0 (exploratório)**
- Dashboard web para triagem multi-host
- Suporte a macOS
---
## Contribuindo
As contribuições mais úteis no momento são novas regras de detecção (adicionadas como funções standalone em `detectors/rules.py` com um teste correspondente), coletores adicionais para tipos de artefatos ainda não cobertos, módulos de remediação para `SUSPICIOUS_SYSTEMD_TIMER` e `SHELL_RC_MODIFICATION` (ambos atualmente apenas sinalizados por design, mas caminhos seguros de auto-remediação podem existir), casos de teste para casos extremos em configurações específicas do Ubuntu, e melhorias na documentação.
Abra uma issue antes de iniciar uma contribuição grande para evitar trabalho duplicado.
---
## Licença
MIT
---
## Autor
Construído por Asmit — BTech Computer Science, PES University, Bengaluru. A ferramenta surgiu da frustração com quanto tempo a triagem manual do Ubuntu leva em comparação com o que um script bem delimitado pode automatizar.
SHELL_RC_MODIFICATION--root