
CLI/TUI de Python para triaje forense de sistemas Ubuntu — detecta y remedia mecanismos de persistencia con recolección de artefactos, correlación de líneas de tiempo e integración con Wazuh.
Cuando sospechas que un sistema Linux ha sido comprometido, los primeros 30-40 minutos suelen dedicarse a ejecutar los mismos diez comandos en secuencia: revisar los procesos en ejecución, buscar tareas cron extrañas, hacer grep de LD_PRELOAD, escanear authorized_keys en busca de nuevas entradas, auditar sudoers. Cada paso es manual, implica cambiar de contexto y es propenso a errores bajo presión. Si te saltas una fuente — por ejemplo, /etc/sudoers.d/ en lugar de solo /etc/sudoers, o los crontabs de usuario además de /etc/cron.d/ — tendrás una imagen incompleta.
Las opciones existentes no resuelven esto de forma limpia. lynis es un auditor de endurecimiento, no una herramienta de triaje — informa debilidades de configuración en un sistema limpio y genera ruido en uno comprometido. chkrootkit y rkhunter buscan firmas de rootkits conocidos, pero son ciegos a técnicas de persistencia novedosas como temporizadores systemd abusados o entradas cron con apariencia legítima. Las consultas genéricas a un SIEM requieren una infraestructura de logs que puede no existir en el sistema que estás examinando. Y las suites forenses como Volatility se orientan a imágenes de memoria, no a una shell en vivo sobre un host en ejecución.
El vacío es una herramienta que se ejecute en el sistema en vivo ahora mismo, cubra los vectores de persistencia más comunes, correlacione la actividad entre fuentes de logs en una línea temporal y te diga exactamente qué revisar — sin requerir un agente externo, una base de datos ni conexión a internet.
ubuntils se ejecuta en cuatro etapas secuenciales:
/proc, tablas cron, unidades systemd, claves SSH, archivos sudoers, definiciones de entorno, integridad de paquetes (dpkg --verify), configuración de PAM/NSS y módulos del kernel cargados. Tarda aproximadamente 2,5 segundos en un sistema típico.--rules — sobre los artefactos recolectados, produciendo una lista de hallazgos clasificada y con puntuación de confianza en aproximadamente un segundo.--json).ubuntils en sí no realiza llamadas de red, y todas las funciones — incluidas las reglas personalizadas y la correlación — se ejecutan contra artefactos recolectados localmente. La única forma en que los hallazgos pueden salir del host es la integración con Wazuh: si hay un agente Wazuh instalado, un scan en vivo escribe sus hallazgos en un archivo local que el agente luego envía a su gestor. Pasa --no-wazuh para desactivarlo durante una ejecución.
ubuntils scan no se ve afectado por todo lo que sigue — sigue siendo 100% en vivo, de un solo host, y cada flag existente funciona de forma idéntica. Dos comandos adicionales, collect y analyze, dividen el mismo pipeline de detección/línea temporal en un flujo de trabajo de adquirir-y-analizar apto para uso offline, para casos en los que no puedes (o no quieres) ejecutar la detección directamente en el host bajo investigación — consulta Análisis offline: collect y analyze más abajo, incluidas sus advertencias sobre la cobertura de detección.
Ubuntu 22.04+ y cualquier sistema con PEP 668 (recomendado):
Ubuntu 22.04+ bloquea pip install a nivel de sistema. Usa pipx — gestiona el entorno de forma transparente para que nunca tengas que pensar en ello:```bash
sudo apt install pipx -y
cd ubuntils
pipx install -e .
ubuntils scan
**Sistemas más antiguos / instalación manual:**```bash
git clone https://github.com/asmitdesai/ubuntils.git
cd ubuntils
pip install -r requirements.txt -e .
ubuntils scan
ubuntils requiere root para acceder completamente a los artefactos. Si ejecutas ubuntils scan como un usuario no root, se reinvocará automáticamente con sudo usando el mismo intérprete de Python (por ruta absoluta), de modo que se utilice el entorno correcto sin reenviar tu PATH al proceso root. Cada comando externo (ss, dpkg, systemctl, …) se resuelve en una ruta de búsqueda fija propiedad de root, nunca tu PATH. Ejecutarlo sin root omitirá /etc/shadow, algunas entradas de /proc y archivos cron protegidos, y registrará advertencias por cada uno.
Solo detección — TUI interactiva:```bash sudo ubuntils scan
**Detección con salida JSON guardada en archivo:**```bash
sudo ubuntils scan --json > /tmp/triage-$(hostname)-$(date +%Y%m%d).json
Detección con vista previa de remediación mediante CLI (ejecución en seco — sin aplicar cambios):```bash sudo ubuntils scan --remediate
**Detección con remediación de CLI aplicada:**```bash
sudo ubuntils scan --remediate --confirm
Versión para imprimir:```bash ubuntils version
**Recopila un paquete a prueba de manipulaciones para su posterior análisis o análisis sin conexión:**```bash
sudo ubuntils collect --output /path/to/bundle.tar.gz
Analizar un paquete recopilado previamente (no se requiere root):```bash ubuntils analyze /path/to/bundle.tar.gz --json
**Analizar una imagen forense montada o un árbol de sistema de archivos extraído en lugar de un paquete:**```bash
ubuntils analyze --root /mnt/forensic-image --json
Consulta Análisis offline: recopilar y analizar para conocer el formato del paquete y — lo que es más importante — qué no puede detectar el análisis offline en comparación con un scan en 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
### Lista blanca de falsos positivos (`--config`)
Un host recién aprovisionado o gestionado por CI genera ruido esperado — claves de despliegue, crontabs de aprovisionamiento, inicialización de shell preconfigurada. En lugar de enseñar a los respondedores a filtrarlo mentalmente, suprimirlo explícitamente con una lista blanca 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
| -s | --server | SERVER | http://localhost:8080 | URL del servidor de escaneo |
| -t | --token | TOKEN | - | Token de autenticación |
| -o | --output | FILE | - | Archivo de salida |
| -f | --format | FORMAT | json | Formato de salida (json, yaml, csv) |
| -v | --verbose | - | false | Habilitar registro detallado |
| -q | --quiet | - | false | Modo silencioso |
| --timeout | - | SECONDS | 30 | Tiempo de espera de la solicitud |
| --retry | - | COUNT | 3 | Número de reintentos |
| --insecure | - | - | false | Omitir la verificación del certificado TLS |
# Escaneo básico
scanner scan --target example.com
# Escaneo con autenticación
scanner scan --target example.com --token $API_TOKEN
# Salida en formato JSON
scanner scan --target example.com --format json --output results.json
# Escaneo con tiempo de espera personalizado
scanner scan --target example.com --timeout 60 --retry 5
La herramienta se puede configurar mediante un archivo de configuración o variables de entorno.
Cree un archivo config.yaml en el directorio de trabajo:
server: http://localhost:8080
token: your-api-token
timeout: 30
retry: 3
output:
format: json
file: results.json
| Variable | Descripción | Valor predeterminado |
|---|---|---|
SCANNER_SERVER | URL del servidor de escaneo | http://localhost:8080 |
SCANNER_TOKEN | Token de autenticación | - |
SCANNER_TIMEOUT | Tiempo de espera de la solicitud | 30 |
SCANNER_RETRY | Número de reintentos | 3 |
SCANNER_INSECURE | Omitir la verificación del certificado TLS | false |
name: Security Scan
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run security scan
run: |
scanner scan --target example.com --format json --output results.json
- name: Upload results
uses: actions/upload-artifact@v3
with:
name: scan-results
path: results.json
security-scan:
stage: test
script:
- scanner scan --target example.com --format json --output results.json
artifacts:
paths:
- results.json
Error: No se puede conectar al servidor
Verifique que el servidor de escaneo esté en ejecución y sea accesible:
curl -v http://localhost:8080/health
Error: Token de autenticación no válido
Asegúrese de que el token sea correcto y no haya expirado:
scanner auth verify --token $API_TOKEN
Error: Tiempo de espera de la solicitud agotado
Aumente el valor de tiempo de espera:
scanner scan --target example.com --timeout 120
Habilite el registro detallado para solucionar problemas:
scanner scan --target example.com --verbose
Las contribuciones son bienvenidas. Por favor, consulte CONTRIBUTING.md para obtener más detalles.
Este proyecto está licenciado bajo la Licencia MIT. Consulte el archivo LICENSE para obtener más detalles.```bash sudo ubuntils scan --json --config allowlist.yaml
La supresión siempre es explícita — por id de regla y/o ruta exacta del artefacto. No existe un interruptor general de "ignorar todo". Hay un ejemplo en [`examples/allowlist.yaml`](https://github.com/asmitdesai/ubuntils/blob/main/examples/allowlist.yaml).
### Establecimiento de línea base de elementos conocidos como buenos (`--baseline`)
`--config` suprime una regla o ruta *en todas partes, para cualquiera que ejecute ubuntils contra este código base*. `--baseline` es más acotado y específico del entorno: indica que "en *este* entorno, este artefacto exacto — esta clave SSH, este archivo RC — se sabe que es bueno", sin silenciar la regla o la ruta para todos los demás hosts que escanees con la misma herramienta. Se mantiene como un archivo separado de `--config` por la misma razón por la que `--rules` es independiente: la supresión y la inclusión en lista de permitidos a nivel de regla son preocupaciones distintas que no deberían convivir en un solo archivo.```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
-h, --help | Muestra el mensaje de ayuda y sale |
-v, --version | Muestra la versión del programa y sale |
-d, --debug | Habilita el registro de depuración |
-c, --config | Ruta al archivo de configuración |
-o, --output | Ruta del archivo de salida |
-f, --format | Formato de salida (json, xml, csv) |
-q, --quiet | Modo silencioso, suprime la salida |
-V, --verbose | Modo detallado, aumenta la salida |
| sudo ubuntils scan --json --baseline baseline.yaml |
Una entrada de línea base coincide por `rule_id` más un `fingerprint` probado como una subcadena del `raw_value` del hallazgo o una coincidencia exacta contra su `artifact_path`. Una coincidencia elimina el hallazgo del informe por completo — la supresión nunca es silenciosa, sin embargo: el número de hallazgos que una línea base eliminó siempre es visible en `scan_metadata.suppressed_by_baseline`. La supresión mediante lista de permitidos (`--config`) sigue aplicándose por encima de la supresión de línea base. Funciona de forma idéntica en `scan` y `analyze`. Hay un ejemplo en [`examples/baseline.yaml`](https://github.com/asmitdesai/ubuntils/blob/main/examples/baseline.yaml).
### Reglas de detección personalizadas (`--rules`)
`--config` *suprime* hallazgos; `--rules` los *añade*. Son archivos deliberadamente separados porque son preocupaciones opuestas.
Un archivo de reglas es solo coincidencia de patrones — sin expresiones, sin condicionales y sin ejecución de código, por lo que cargar uno nunca puede ejecutar lógica proporcionada por un atacante. Cada regla nombra un `source` de artefacto, un modo de `match` y un `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
| --no-color | Desactiva la salida en color |
| --debug | Habilita el registro de depuración |
| --verbose | Habilita el registro detallado |
| --version | Muestra la versión del programa y sale |
| --help | Muestra el mensaje de ayuda y sale |
# Escanear un objetivo con salida en formato JSON
python3 CVE-Scanner.py -u https://example.com -o json
# Escanear múltiples objetivos desde un archivo con 10 hilos
python3 CVE-Scanner.py -f targets.txt -t 10
# Escanear con un proxy y guardar los resultados en un archivo
python3 CVE-Scanner.py -u https://example.com -p http://127.0.0.1:8080 -o output.txt
La herramienta genera un informe detallado que incluye:
¡Las contribuciones son bienvenidas! Por favor, sigue estos pasos:
git checkout -b feature/nueva-funcionalidad)git commit -am 'Añade nueva funcionalidad')git push origin feature/nueva-funcionalidad)Este proyecto está licenciado bajo la Licencia MIT - consulta el archivo LICENSE para más detalles.
Esta herramienta está destinada únicamente a fines educativos y de pruebas de seguridad autorizadas. Los autores no se hacen responsables del uso indebido o de los daños causados por esta herramienta. Utilízala únicamente en sistemas que poseas o para los que tengas permiso explícito de probar.
| `source` | Se compara con |
|---|---|
| `cron` | El comando cron (ruta: el archivo crontab) |
| `environment` | La línea sin procesar de entorno/inicialización del shell (ruta: el archivo que la define) |
| `ssh` | Tipo de clave, datos de la clave y comentario (ruta: el archivo `authorized_keys`) |
| `process` | La línea de comandos del proceso (ruta: la ruta del ejecutable) |
| `network` | La descripción de la conexión (ruta: `remote_addr:remote_port`) |
`regex` y `substring` coinciden con la columna de texto; `glob` coincide con la columna de ruta — por lo que un glob de `network` como `203.0.113.*:*` apunta al endpoint remoto. Los hallazgos de reglas personalizadas son solo de marcado (nunca se remedian automáticamente) y siguen estando sujetos a la supresión de `--config`. Hay un ejemplo en [`examples/custom_rules.yaml`](https://github.com/asmitdesai/ubuntils/blob/main/examples/custom_rules.yaml).
### Integridad del informe
Cada informe `--json` incluye un campo `report_sha256` — un SHA-256 sobre el contenido canónico del informe. Esto hace que un artefacto de triaje recopilado sea a prueba de manipulación y te permite referenciar un escaneo específico por su digest en un expediente. El informe también registra `tool_version`, `hostname` y una marca de tiempo UTC `generated_at` bajo `scan_metadata`. Para `scan`, `hostname`/`ubuntu_version` describen la máquina en la que se está ejecutando `ubuntils`; para `analyze --root` se leen del propio `/etc/hostname` y `/etc/os-release` de la imagen. Para `analyze BUNDLE`, en cambio, provienen del propio manifiesto del bundle — el host que fue *recopilado*, no el host que ejecuta `analyze` — junto con `collection_run_id` y los `collected_at_utc_start`/`collected_at_utc_end` de la recopilación, de modo que el registro de cadena de custodia del informe sigue a la evidencia en lugar de a la estación de trabajo del analista.
**Verificar un informe.** El informe se emite en forma canónica (claves ordenadas, sangría de 2 espacios), y el digest cubre todo excepto el propio `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
ubuntils scan ejecuta la recopilación, la detección y la línea de tiempo juntas contra el host en vivo. collect y analyze dividen ese flujo de trabajo en dos: collect adquiere un paquete a prueba de manipulación de un host (no se ejecuta detección), y analyze ejecuta la misma línea de detección/línea de tiempo utilizada por scan contra un paquete, o contra una imagen montada mediante --root, sin requerir root y sin volver a tocar el host original. Esto es para casos en los que deseas adquirir artefactos una vez y analizarlos más tarde, en otro lugar o repetidamente — o cuando estás realizando un triaje de una imagen de disco en lugar de un sistema en ejecución.
sudo ubuntils collect --output /path/to/bundle.tar.gz
Requiere root, como `scan`. Lee una lista fija de archivos (`/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`) y ejecuta una lista fija de comandos (`ss -tunap`, `netstat -tunap`, `systemctl list-timers` tanto en formato JSON como de texto, y `journalctl -o json` de los últimos 7 días), calcula el hash de cada elemento capturado y escribe todo, junto con un `manifest.json`, en un paquete `.tar.gz`. Los archivos de registro capturados y la salida de journalctl son lo que permite a `analyze BUNDLE` construir una línea de tiempo real sin conexión. Si se omite `--output`, el paquete se escribe en `./ubuntils-bundle-<UTC timestamp>.tar.gz` en el directorio actual.
### `ubuntils analyze````bash
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]
Toma una ruta de bundle como argumento posicional o --root PATH apuntando a una imagen montada / árbol de sistema de archivos extraído — no ambos. Ejecuta el mismo motor de detección, reglas personalizadas, allowlist y lógica de baseline que usa scan. No requiere root. El bundle se extrae a un directorio temporal privado que se elimina tan pronto como finaliza el análisis (puede contener /etc/shadow).
La cobertura difiere entre los dos modos offline, porque un bundle lleva el estado capturado reproducido desde el momento de collect mientras que --root solo tiene lo que esté presente en el sistema de archivos montado:
analyze BUNDLE reproduce la salida real de los comandos ss/systemctl list-timers/journalctl capturada en el momento de collect, por lo que NetworkCollector y SystemdCollector producen hallazgos genuinos a partir de esa instantánea — no se omiten. La línea de tiempo se construye a partir del syslog/messages/audit.log/journalctl que el bundle capturó, por lo que está completamente poblada y los hallazgos obtienen una correlación real de related_events.analyze --root PATH apunta a una imagen muerta, montada, sin proceso activo ni estado del kernel que consultar, por lo que la ejecución de comandos se deshabilita por completo: NetworkCollector y SystemdCollector se omiten, registrándose en scan_metadata.command_collectors_skipped. La línea de tiempo aún se construye, pero a partir de los archivos de log estáticos presentes en la imagen (/var/log/syslog, /var/log/messages, /var/log/audit/audit.log) — la reproducción de journald no está disponible aquí ya que no hay un journalctl activo para consultar una imagen muerta.Consulta Offline analysis: collect and analyze para la lista completa de brechas de cobertura de detección offline.
Un bundle es un tarball comprimido con gzip con todo bajo un prefijo 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 de `manifest.json`:
| Campo | Tipo | Descripción |
|---|---|---|
| `run_id` | string | UUID generado de nuevo para cada ejecución de `collect` |
| `host_id` | string | Reservado para futura correlación multi-host; actualmente vacío |
| `hostname` | string | `socket.gethostname()` en el momento de la recolección |
| `ubuntu_version` | string | Cadena de versión de Ubuntu detectada |
| `collected_at_utc_start` / `collected_at_utc_end` | string (ISO 8601) | Límites de tiempo real de la ejecución de recolección |
| `tool_version` | string | Versión de ubuntils que produjo el bundle |
| `files[]` | array | Una entrada por archivo capturado: `source_path`, `bundle_path`, `sha256`, `size`, `mtime`, `ctime` (un archivo ausente o ilegible en el host de origen se registra con `sha256: ""`, `size: -1` en lugar de abortar la recolección) |
| `commands[]` | array | Una entrada por comando capturado: `name`, `argv`, `bundle_path`, `sha256`, `exit_code` |
| `bundle_sha256` | string | SHA-256 sobre el resto del manifiesto (todo lo anterior, serializado canónicamente) — el ancla de evidencia de manipulación para todo el bundle |
### Integridad del bundle en la salida JSON
`scan_metadata.bundle_integrity` de `analyze` reporta uno de tres valores:
- `"live"` — `scan` y `analyze --root` reportan esto; no hay bundle que verificar.
- `"ok"` — `analyze BUNDLE` verificó `bundle_sha256` contra el manifiesto y el SHA-256 de cada archivo capturado contra su contenido en el bundle; nada ha sido alterado desde que `collect` lo escribió.
- `"mismatch"` — el digest del manifiesto, el hash de un archivo capturado o el hash de la salida de un comando capturado no coincidieron. Algo en el bundle fue modificado, truncado o corrompido después de la recolección, y nada derivado de él debe considerarse limpio en cuanto a cadena de custodia. `analyze` aún produce su informe, pero imprime una advertencia roja en stderr, muestra un banner de integridad en la parte superior de la pestaña Summary de la TUI, y **sale con estado 3**, para que los scripts no puedan confundir los resultados de un bundle manipulado con resultados autoritativos.
### ⚠️ El análisis offline tiene lagunas de detección reales — lea esto antes de confiar en él
**Una ejecución de `analyze` con origen en un bundle o en `--root` no tiene paridad de detección con un `scan` en vivo.** Estos no son casos límite; son limitaciones estructurales de la adquisición estática y offline, y producen menos hallazgos (o ninguno) para las reglas afectadas en lugar de un error. Cuando un colector *sabe* que no pudo mirar (un comando fallido, un archivo ilegible), eso se registra en `scan_metadata.collectors_degraded` y se señala en la pestaña Summary de la TUI — pero un archivo que simplemente no fue capturado se ve igual que un archivo que no existe. (La línea de tiempo en sí ya *no* es una de estas lagunas: `analyze BUNDLE` reproduce el syslog/messages/audit.log/journalctl capturado desde el momento de `collect`, y `analyze --root` lee los archivos de log estáticos presentes en la imagen montada, por lo que ambos producen una línea de tiempo real y una correlación real de `related_events` — véase [`ubuntils analyze`](#ubuntils-analyze) más arriba.)
- **`PROCESS_MASQUERADE` y `PROCESS_SUSPICIOUS_CONNECTION` siempre reportarán cero hallazgos en modo offline.** Ambas reglas se basan en el campo `exe` de un proceso, que se completa leyendo el destino del enlace simbólico `/proc/<pid>/exe` a través de la fuente de artefactos (nunca el propio `/proc` del analista). Un bundle no tiene un `/proc` en vivo que leer, y `--root` apunta a un árbol de sistema de archivos montado que tampoco tiene `/proc` — actualmente no existe ningún mecanismo para capturar o reconstruir offline un destino de enlace simbólico de exe resuelto, por lo que `exe` siempre está vacío y ambas reglas nunca se activan, independientemente de lo que realmente haya en el host.
- **La enumeración de procesos no ocurre en absoluto offline.** `collect` no tiene un paso de captura por PID (`/proc/*/status`, `/proc/*/cmdline`), por lo que no existen procesos en un bundle que analizar en primer lugar — esta es la misma causa raíz que el punto anterior, desde el lado de la adquisición.
- **`CRON_TMP_PATH`, `SUDOERS_NOPASSWD` y `SSH_UNAUTHORIZED_KEY` están limitadas o ausentes en el análisis con origen en bundle.** La lista de archivos de `collect` es estática y no puede expandir con glob `/etc/cron.d/*`, `/etc/sudoers.d/*`, `/etc/profile.d/*`, ni `~/.ssh/authorized_keys` por usuario — solo se capturan `/etc/crontab`, `/etc/sudoers` y `/etc/environment`/`/etc/profile`. (`--root` contra un árbol de sistema de archivos montado completo no tiene esta laguna, ya que los directorios reales están presentes en el disco.) Cuando `SSH_UNAUTHORIZED_KEY` o `SHELL_RC_MODIFICATION` *sí* se activan (scan en vivo, o `--root` con los directorios reales por usuario presentes), ahora también puntúan la confianza a partir del ctime y el contenido del archivo, no solo del mtime — véase [Puntuación de confianza](#json-output) más abajo. Eso mejora cuánto debería confiar en un hallazgo que sí se activa; no cambia si la regla se activa offline en primer lugar.
- **La detección de `SUSPICIOUS_SYSTEMD_TIMER` está debilitada para bundles.** Las unidades de servicio se leen directamente de los directorios de unidades (`/etc/systemd/system`, `/usr/lib/systemd/system`, `~/.config/systemd/user` por usuario, …), por lo que `--root` obtiene cobertura completa de servicios. Un bundle no captura esos directorios, sin embargo: los timers aparecen a partir de la salida capturada de `systemctl list-timers`, pero el `ExecStart` de cada timer proviene de una llamada `systemctl show` por unidad que `collect` no realiza, por lo que la regla no puede evaluar qué ejecuta un timer incluido en el bundle.
**Cuándo importa:** si está triando un host en vivo y accesible, use `sudo ubuntils scan` — tiene cobertura de detección completa. Use `collect`/`analyze` cuando necesite adquirir una vez y analizar en otro lugar, necesite analizar sin root, o esté trabajando desde una imagen de disco donde `scan` no es una opción en absoluto — y trate un resultado limpio de `analyze` para las reglas anteriores como "no verificado", no como "verificado y limpio".
---
## La TUI
Ejecutar `sudo ubuntils scan` (sin `--json`) lanza una TUI interactiva a pantalla completa.
### Pantalla de escaneo
Mientras los colectores se ejecutan, ubuntils muestra una lista de verificación en vivo — una fila por colector. Cada fila se actualiza en tiempo real a medida que el colector termina:```
Scanning system…
✓ Process
✓ Network
✓ Users
⠹ Cron
Systemd
SSH
Sudoers
Environment
✓ marca éxito, ✗ marca fallo, el spinner marca el colector activo, y las filas en blanco están pendientes. Una vez que todos los colectores terminan y la detección + línea de tiempo se completan, la TUI cambia automáticamente a la pantalla de resultados.
La pantalla de resultados tiene cuatro pestañas que se navegan con las teclas numéricas:
| Tecla | Pestaña | Contenido |
|---|---|---|
1 | Resumen | Estadísticas del escaneo + hallazgos principales de un vistazo |
2 | Hallazgos | Lista completa de hallazgos con detalle en línea y remediación |
3 | Línea de tiempo | Eventos de registro correlacionados cronológicamente |
4 | Estadísticas | Versión de Ubuntu, arquitectura, duración, recuentos de colectores |
Pulse q o Ctrl+C para salir.
1)Muestra los metadatos del escaneo y los hallazgos principales en una sola pantalla:``` 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
En un sistema limpio, esta pestaña muestra `System appears clean.`
### Pestaña Findings (tecla `2`)
Una lista desplazable de todos los hallazgos, ordenados de HIGH → MEDIUM → LOW. Al seleccionar un hallazgo (Enter o las teclas de flecha) se expande un panel de detalles en la parte inferior que muestra la descripción completa, la ruta del artefacto, el valor desencadenante sin procesar y la información de remediación.```
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
Para los hallazgos que tienen disponible una remediación automatizada, presione R mientras el hallazgo está seleccionado. Aparece un modal de confirmación:```
┌─────────────────────────────────────────────────┐
│ 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 │
└─────────────────────────────────────────────────┘
Pulsa `Y` para confirmar. El remediador se ejecuta en un hilo en segundo plano. Cuando finaliza, la fila del hallazgo en la lista se actualiza a `[fixed]` y el panel de detalles muestra el 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
En caso de fallo, el panel de detalles muestra el error. La copia de seguridad siempre se crea antes de intentar cualquier cambio.
Pulse Esc para contraer el panel de detalles.
3)Una lista cronológica desplazable de eventos de registro correlacionados. Cada fila muestra la marca de tiempo, el origen y la descripción. Los eventos se extraen de syslog, journald y auditd y se deduplican.
4)Una vista resumida del escaneo: versión de Ubuntu detectada, arquitectura, duración del escaneo, número de recopiladores y fallos, y recuento de hallazgos por severidad junto con el total de eventos de la línea de tiempo.
| ID de regla | Severidad | Subsanable | Qué comprueba |
|---|---|---|---|
| CRON_ROOT_EXEC | HIGH | Sí | Crontabs de usuarios no root que ejecutan comandos en rutas propiedad de root o incluyen sudo en línea |
| CRON_TMP_PATH | HIGH | Sí* | Cualquier tarea cron (incluidas las entradas @reboot/@daily y los scripts en /etc/cron.{hourly,daily,weekly,monthly}) que haga referencia a /tmp, /var/tmp o /dev/shm. *Las líneas de script son solo de marcado |
| LD_PRELOAD_INJECT | HIGH | Sí | Cualquier entrada en /etc/ld.so.preload (vacío en Ubuntu estándar), o LD_PRELOAD en cualquier archivo de inicio de shell con cualquier biblioteca listada fuera de /lib, /usr/lib, /lib64, /usr/lib64 |
| SUSPICIOUS_SYSTEMD_TIMER | HIGH | No | Temporizadores de systemd y unidades de servicio cuyo ExecStart haga referencia a un directorio con permisos de escritura para todos o ejecute un binario que no sea propiedad de root |
| SSH_UNAUTHORIZED_KEY | MEDIUM | Sí | Archivos authorized_keys modificados en los últimos 7 días |
| USER_UID_ZERO | HIGH | No | Cualquier cuenta distinta de root con UID 0 (un segundo superusuario oculto) |
| USER_EMPTY_PASSWORD | HIGH | No | Una cuenta con shell de inicio de sesión cuyo campo de contraseña en /etc/shadow esté vacío (el nullok predeterminado de PAM en Ubuntu permite iniciar sesión sin contraseña) |
| SUDOERS_NOPASSWD | MEDIUM | Sí* | Concesiones NOPASSWD en sudoers para usuarios con UID ≥ 1000 y shell de inicio de sesión, directamente o mediante una regla %group; se siguen los archivos incluidos. *Las reglas de grupo son solo de marcado (eliminar %sudo podría eliminar todo el acceso a sudo) |
| PROCESS_MASQUERADE | MEDIUM | No | Procesos cuyo nombre coincide con un binario conocido del sistema pero cuyo ejecutable está fuera de los directorios estándar del sistema (/usr/bin, /usr/sbin, /bin, /sbin, /usr/local/{bin,sbin}, /usr/lib, /usr/libexec, /lib, /snap) |
| PROCESS_SUSPICIOUS_CONNECTION | HIGH / MEDIUM | No | Procesos que mantienen una conexión saliente cuyo ejecutable está en un directorio temporal con permisos de escritura para todos o ha sido eliminado del disco (HIGH), o está fuera de los directorios estándar del sistema o se comunica con un puerto remoto no estándar (MEDIUM) |
CRON_ROOT_EXEC — Los crontabs de usuario se ejecutan como el propietario del crontab. Una entrada que invoca sudo o un intérprete propiedad de root significa que el usuario ha dispuesto que se ejecute código con privilegios de root de forma programada, sin necesidad de acceso sudo persistente. Esto sobrevive a los cambios de contraseña.
Ejemplo de hallazgo:``` [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** — Los directorios con permisos de escritura para todos, como /tmp y /dev/shm, son zonas de preparación estándar para atacantes. Un trabajo cron que apunta allí significa que un payload puede ser sustituido entre invocaciones sin tocar ninguna ruta persistente. Esto cubre las entradas de estilo `@reboot`/`@daily` y los scripts en `/etc/cron.{hourly,daily,weekly,monthly}`; los hallazgos sobre esos scripts son solo de marcado, ya que eliminar una línea de un script de shell no es una corrección automática segura.
*Ejemplo de hallazgo:*```
[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 hace que el enlazador dinámico cargue una biblioteca compartida especificada antes que todas las demás, lo que permite la interceptación arbitraria de funciones en cualquier binario enlazado dinámicamente. Un valor que apunte fuera de las rutas de bibliotecas estándar es un indicador de rootkit en espacio de usuario casi seguro; se verifica cada biblioteca en una lista separada por espacios o dos puntos. /etc/ld.so.preload se inyecta en todos los procesos y está vacío en Ubuntu estándar, por lo que cualquier entrada allí se reporta — incluso una colocada dentro de /lib, un truco común de rootkit. La remediación elimina las entradas de /etc/ld.so.preload en lugar de comentarlas, porque el cargador no tiene sintaxis de comentarios en ese archivo.
Ejemplo de hallazgo:``` [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** — Los temporizadores de Systemd son más persistentes y menos visibles que los trabajos de cron para la mayoría de los respondedores. Un temporizador — o una unidad `.service` simple, que es la persistencia más común y no necesita ningún temporizador — cuyo ExecStart haga referencia a un directorio temporal o ejecute un binario que no sea propiedad de root es señal de persistencia creada por el atacante. Las unidades de servicio se leen directamente de los directorios de unidades, incluido el directorio por usuario `~/.config/systemd/user`. Solo marca — la eliminación de unidades de systemd requiere criterio humano.
*Ejemplo de hallazgo:*```
[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 — Una clave SSH recién añadida otorga acceso remoto persistente independientemente de las contraseñas. La ventana de 7 días detecta adiciones recientes mientras evita el ruido del aprovisionamiento inicial en sistemas más antiguos. Nota: la regla utiliza el mtime del archivo, que refleja la última escritura en el archivo authorized_keys, no la marca de tiempo de inserción de cada clave individual.
Ejemplo de hallazgo:``` [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 sin contraseña para una cuenta de usuario humana (UID ≥ 1000 con un shell de inicio de sesión) es un vector de escalada de privilegios que sobrevive a la eliminación de otros mecanismos de persistencia. Las concesiones NOPASSWD legítimas son casi siempre para cuentas de servicio sin shell de inicio de sesión. Las reglas de grupo (`%sudo ALL=(ALL) NOPASSWD:ALL`) se resuelven a sus miembros, y los archivos `#include`/`@includedir` se siguen. Los hallazgos de grupo son solo de marcado: eliminar una regla como `%sudo` podría eliminar todas las concesiones de sudo en el sistema.
*Ejemplo de hallazgo:*```
[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 — Nombrar un binario malicioso con el nombre de un proceso del sistema conocido (sshd, python3, bash) es una técnica básica para evitar la detección en la salida de ps. Esta regla cruza el nombre del proceso de /proc/<pid>/status con la ruta resuelta del ejecutable de /proc/<pid>/exe. Las ubicaciones estándar incluyen /usr/local/{bin,sbin}, /usr/lib, /usr/libexec y /snap, por lo que systemd (/usr/lib/systemd/systemd) y los paquetes snap no la activan. Solo marca — matar un proceso requiere criterio humano.
Ejemplo de hallazgo:``` [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** — Solo `root` debería tener el UID 0. Una segunda cuenta asignada al UID 0 (CIS Ubuntu Benchmark 6.2.x) es una puerta trasera de alta confianza: otorga derechos de superusuario completos sin alterar las propias credenciales de root, y sobrevive a un restablecimiento de la contraseña de root. Tasa de falsos positivos casi nula. Solo marcado — eliminar una cuenta con UID 0 requiere criterio humano.
*Ejemplo de hallazgo:*```
[HIGH] USER_UID_ZERO
Title: Non-root account with UID 0
Artifact: /etc/passwd
Raw value: toor:x:0:0:...:/bin/bash
Remediation: not available
USER_EMPTY_PASSWORD — Una cuenta cuyo campo de contraseña en /etc/shadow está vacío no tiene contraseña alguna, y la pila PAM predeterminada de Ubuntu (pam_unix ... nullok) le permite iniciar sesión sin una. En una cuenta con un shell de inicio de sesión, eso es una puerta abierta. Solo marca — bloquéala con passwd -l mientras investigas.
Ejemplo de hallazgo:``` [HIGH] USER_EMPTY_PASSWORD Title: Login account with no password Artifact: /etc/shadow Raw value: eve:: Remediation: not available
**PROCESS_SUSPICIOUS_CONNECTION** — La persistencia es solo la mitad del panorama; un punto de apoyo que nunca se comunica con nada rara vez es el que te importa. Esta regla une los recolectores de procesos y de red por PID, de modo que un proceso marcado llega con sus conexiones actuales adjuntas. Un ejecutable preparado en `/tmp` que mantiene un socket saliente establecido es HIGH; un binario legítimo que alcanza un puerto remoto no estándar es MEDIUM y merece una revisión. Esta es una instantánea del estado actual, no un monitoreo continuo — un beacon que está dormido cuando escaneas no aparecerá. Solo marcado.
*Ejemplo de hallazgo:*```
[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 — Los archivos de inicialización del shell son un vector de persistencia fiable porque se ejecutan en cada inicio de sesión del usuario. Esta regla muestra las modificaciones recientes para revisión humana. Solo marca — el contenido del RC del shell requiere lectura antes de actuar sobre él.
Ejemplo de hallazgo:``` [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** — Los binarios y archivos de configuración propiedad del sistema son la base de la confianza. Esta regla detecta cuándo los archivos propiedad de paquetes han sido modificados, eliminados o presentan discrepancias de contenido/modo/tamaño usando `dpkg --verify`. Las ediciones solo de conffile (cambios de configuración local esperados) se excluyen del reporte para evitar ruido. Solo marca — la manipulación puede ser legítima (ediciones locales personalizadas) o maliciosa (reemplazo de archivo); decidir requiere juicio humano.
*Ejemplo de hallazgo:*```
[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 — Los atacantes a menudo establecen el flag inmutable (i) o el flag de solo anexado (a) en archivos para impedir su modificación o eliminación, incluso por root, incluyendo para ocultar la manipulación de ediciones posteriores o la rotación de logs. Establecer estos flags en archivos sensibles del sistema como /etc/passwd, /etc/sudoers, /etc/pam.d/*, o los archivos de log auth/syslog/wtmp/btmp es un fuerte indicador de endurecimiento por parte del atacante. Esta regla detecta los flags inmutable y de solo anexado mediante lsattr contra esa lista fija de rutas sensibles. Solo flag — los cambios de flag requieren revisión humana.
Ejemplo de hallazgo:``` [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) y NSS (Name Service Switch) son los sistemas centrales de autenticación e identidad en Linux. Esta regla NO realiza detección basada en mtime de "¿fue modificado este archivo?" — compara patrones en el *contenido* del archivo: (1) una línea literal `pam_permit.so` en cualquier archivo /etc/pam.d/* (este módulo siempre tiene éxito y es un backdoor clásico de omisión de autenticación), o (2) un módulo NSS listado en /etc/nsswitch.conf que no está en la lista de permitidos integrada de ubuntils. **Nota:** la comprobación de NSS dará falsos positivos en hosts unidos a dominio/SSSD/LDAP/Winbind que usen un módulo fuera de la lista de permitidos integrada — por esta razón se puntúa intencionalmente con menor confianza que la coincidencia de pam_permit.so; use `--config` para permitir nombres de módulos no reconocidos en su entorno. Solo marcado — los cambios en la configuración de autenticación requieren una verificación cuidadosa.
*Hallazgos de ejemplo:*```
[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 — Los módulos del kernel se ejecutan en ring 0 con acceso sin restricciones. Los atacantes cargan con frecuencia módulos del kernel personalizados para rootkits, sniffing de paquetes u ocultación de procesos. Esta regla compara los módulos cargados actualmente con una pequeña lista de permitidos de módulos integrados esperados (comunes en la mayoría de los sistemas). Es de severidad LOW porque esa lista de permitidos es deliberadamente reducida. Nota: los hosts con mucho hardware, como controladores de GPU, tarjetas Wi-Fi o controladores propietarios, generarán falsos positivos. Los respondedores deben agregar los módulos esperados de su host mediante --config, permitiéndolos por nombre de módulo (usado como artifact_path). Solo marcado — la investigación de módulos del kernel requiere herramientas forenses y experiencia humana.
Ejemplo de hallazgo:``` [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** — Los binarios setuid y setgid escalan privilegios automáticamente cuando se ejecutan. Los atacantes crean binarios setuid/setgid personalizados para persistir la escalada de privilegios, con mayor frecuencia fuera de los directorios estándar de binarios del sistema (una ruta de instalación convencional como /opt, el propio /home de un usuario, /srv, o un directorio temporal con permisos de escritura para todos). Esta regla inventaría los binarios setuid/setgid mediante `find -perm -4000 -o -perm -2000` en `/usr /bin /sbin /opt /home /srv /tmp /var/tmp /dev/shm` y marca cualquiera que esté fuera de un conjunto de referencia conocido de utilidades legítimas del sistema. Solo marca — los binarios setuid/setgid inesperados requieren investigación, pero pueden ser binarios legítimos instalados por aplicaciones.
*Ejemplo de hallazgo:*```
[LOW] SETUID_INVENTORY
Title: Unexpected setuid binary
Artifact: /tmp/.hidden/backdoor
Raw value: setuid
Remediation: not available
--json escribe un único objeto JSON en stdout. No se imprime nada más.```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 una clave adicional de nivel superior solo cuando se pasa `--remediate`. `report_sha256` siempre está presente y se calcula sobre el resto del documento. `scan_metadata.bundle_integrity` es `"live"` para `scan` y `analyze --root`, `"ok"` para un bundle verificado pasado a `analyze`, y `"mismatch"` si el contenido de un bundle no coincide con su manifiesto — consulta [Integridad del bundle en la salida JSON](#bundle-integrity-in-json-output). `scan_metadata.command_collectors_skipped` nombra cualquier colector basado en comandos (`NetworkCollector`, `SystemdCollector`, `PackageCollector`, `KernelCollector`) omitido en una ejecución con `--root` — siempre vacío para `scan` y `analyze BUNDLE`. `scan_metadata.suppressed_by_baseline` es el recuento de hallazgos que un archivo `--baseline` eliminó de este informe — consulta [Línea base de estado conocido](#known-good-baselining---baseline). `scan_metadata.collectors_degraded` asigna un nombre de colector a los motivos por los que sus datos están incompletos (un comando que falló o agotó el tiempo de espera, un archivo ilegible, una línea malformada omitida), `rules_failed` enumera cualquier regla de detección que falló, y `timeline_error` se establece si no se pudo construir la línea de tiempo. Los tres están vacíos en una ejecución saludable. Compruébalos antes de interpretar una lista de hallazgos vacía como "limpio".
`related_events` y `guided_remediation` aparecen en un hallazgo solo cuando tienen contenido. `related_events` contiene hasta cinco eventos de la línea de tiempo emparejados con el hallazgo por ruta de artefacto y palabras clave de la regla, los más recientes primero — una ayuda para sacar a la superficie, no una afirmación causal. `guided_remediation` es una secuencia de comandos revisada para que la ejecutes manualmente; ubuntils nunca la ejecuta.
### Puntuación de confianza
Cada hallazgo lleva una puntuación `confidence` (0–100, por defecto 50) y una `confidence_band` (`HIGH` ≥ 75, `MEDIUM` ≥ 40, `LOW` por debajo de eso), además de una lista `signals` que muestra exactamente cómo se alcanzó esa puntuación — cada entrada es `{"name", "weight", "detail"}`, así que la puntuación siempre es explicable, nunca una caja negra. Las señales son aditivas sobre una confianza base de 50 y las aplica la regla o etapa del pipeline que las produjo:
- Las reglas de detección aplican sus propias señales en el momento del hallazgo — p. ej. `SSH_UNAUTHORIZED_KEY` y `SHELL_RC_MODIFICATION` añaden `content_match` (+30) cuando el contenido del artefacto coincide con un patrón conocido como peligroso (una opción peligrosa de clave SSH; una línea curl/wget-a-shell o base64-decode en un archivo RC de shell), `ctime_corroborates_mtime` (+20) cuando el ctime del archivo también está dentro de la ventana de detección (más difícil de falsificar que el mtime por sí solo), o `mtime_only` (−20) cuando la recencia es la *única* señal y el ctime no la corrobora — una pista de que el mtime puede haber sido retrasado.
- El pipeline aplica `timeline_corroboration` (+25) tras la correlación hallazgo↔línea de tiempo, cuando un hallazgo tiene uno o más `related_events`.
Un hallazgo de banda `LOW` no se descarta ni se oculta — sigue apareciendo en la lista de hallazgos y en la salida JSON exactamente igual que cualquier otro — pero la banda te indica cuánto peso darle antes de investigar más a fondo. Esto reemplaza la antigua heurística basada solo en mtime para `SSH_UNAUTHORIZED_KEY`/`SHELL_RC_MODIFICATION`, donde un archivo obsoleto pero tocado legítimamente (p. ej. una herramienta de gestión de configuración que reescribe `.bashrc` en cada ejecución) parecía idéntico a un backdoor genuinamente nuevo.
**Limitación conocida:** hoy solo están activas las señales de patrón de contenido y ctime. Las señales basadas en propiedad/huella — una huella de clave SSH desconocida, la opción de restricción `from=` de una clave, o una discrepancia de propietario/modo en un archivo RC (una señal `ownership_anomaly`) — aún no están implementadas. Es una brecha de cobertura diferida, registrada para una versión futura, no algo que la puntuación de confianza actual tenga en cuenta.
---
## Remediación
Cinco de las dieciséis reglas de detección tienen remediación automatizada: `CRON_ROOT_EXEC`, `CRON_TMP_PATH`, `LD_PRELOAD_INJECT`, `SSH_UNAUTHORIZED_KEY` y `SUDOERS_NOPASSWD`. El resto son solo de marcado y nunca se remediarán automáticamente, porque actuar sobre ellas de forma segura requiere que una persona lo revise primero.
### Remediación guiada
`SUSPICIOUS_SYSTEMD_TIMER`, `PROCESS_MASQUERADE` y `SHELL_RC_MODIFICATION` llevan una cadena `guided_remediation`: los comandos exactos a ejecutar una vez que hayas confirmado el hallazgo — `systemctl disable --now <unit>`, `kill -9 <pid>`, o la revisión y reversión del archivo RC. Se muestra en el panel de detalle de la TUI y en JSON. ubuntils nunca la ejecuta por ti; estas reglas quedan fuera del barrido `--remediate --confirm` por diseño.
### En la TUI
Selecciona cualquier hallazgo con una remediación en la pestaña Findings, luego pulsa `R`. Un modal de confirmación previsualiza la acción planificada. Pulsa `Y` para aplicarla — el remediador se ejecuta en un hilo en segundo plano para que la TUI siga respondiendo. La fila del hallazgo se actualiza a `[fixed]` cuando termina, con la ruta de la copia de seguridad y el comando de reversión exacto mostrados en línea.
### Desde la CLI
`--remediate` sin `--confirm` es una ejecución en seco segura: se crean copias de seguridad y se ejecuta la validación, pero no se aplica ningún cambio. Pasa ambos flags para realizar cambios realmente. El pipeline se ejecuta antes de que se lance la TUI en este modo, y la pestaña Summary enumera cada resultado de remediación con su ruta de copia de seguridad y comando de reversión.
`--remediate --confirm` solo actúa sobre hallazgos con una puntuación de confianza de al menos 40 (la banda MEDIUM) — un `SSH_UNAUTHORIZED_KEY` de baja confianza y solo mtime se reporta como `SKIPPED` en lugar de eliminar su clave. Ajusta el umbral con `--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
Cada remediación sigue el mismo patrón independientemente de cómo se active:
/var/backups/ubuntils/YYYYMMDD_HHMMSS/ con modo 0700/etc/ld.so.preload se eliminan (el cargador no tiene sintaxis de comentarios allí, por lo que una entrada comentada aún se cargaría). Para sudoers, el contenido editado se verifica con visudo -cf en una copia temporal antes de tocar el archivo real/etc/sudoers truncadoSi algún paso falla, la remediación se detiene inmediatamente, el sistema queda sin cambios, y se reporta el error completo con la ruta de la copia de seguridad y el comando de reversión. El acceso sudo está protegido de dos maneras: las reglas NOPASSWD de %group (como %sudo) son solo de marcado y nunca se eliminan automáticamente, y el remediador de sudoers se niega a eliminar la última regla del archivo sudoers principal.
ubuntils está diseñado para un triaje de un solo host y en un momento puntual — lo ejecutas cuando ya sospechas que algo va mal, y nunca se comunica con el exterior ni sigue vigilando después de que termina el escaneo. Eso es deliberado, pero también significa que un hallazgo de ubuntils vive solo en ese informe a menos que algo lo lleve más allá. La mayoría de los equipos que ejecutan Ubuntu a cualquier escala ya tienen un SIEM haciendo el lado continuo de la detección, así que en lugar de convertir ubuntils en su propio agente de monitorización de larga duración, entrega sus hallazgos al que probablemente ya ejecutas: Wazuh.
Si hay un agente de Wazuh presente en el host (existe /var/ossec/bin/wazuh-agentd o /var/ossec/etc/ossec.conf), ubuntils scan (a menos que se ejecute con --no-wazuh) añade cada hallazgo como una línea JSON a /var/log/ubuntils/wazuh-alerts.json para que el agente lo recoja — esto es un reenviador puro, no un módulo de Wazuh: sin llamada de red, sin clave de API, nada más que las mismas escrituras de artefactos locales que ubuntils ya realiza — aunque el agente, por supuesto, enviará esas líneas fuera del host a su gestor; ese es el objetivo. Se autodetecta sin necesidad de ningún flag (usa --no-wazuh para excluir una ejecución), de modo que un ubuntils scan programado o por script en una flota de hosts enrolados con el agente empieza a alimentar el SIEM inmediatamente sin cableado adicional. Esto nunca ocurre durante el ubuntils analyze offline (bundle o --root), ya que esos hallazgos describen un host diferente al que ejecuta el agente local de Wazuh — reenviar los hallazgos de un bundle al propio agente del analista los atribuiría a la máquina equivocada.
La intención es integrar ubuntils en una canalización de alertas/escalado existente en lugar de pedirle a un respondedor que cuide de una segunda herramienta: una vez cargadas las reglas de ejemplo de abajo, un hallazgo de ubuntils de severidad HIGH (una nueva cuenta UID-0, un rootkit LD_PRELOAD, una puerta trasera PAM) aparece como una alerta normal de Wazuh, hereda cualquier enrutamiento de notificaciones que el gestor ya tenga configurado, y se sitúa junto a cualquier otra señal en la misma línea temporal en lugar de en un archivo JSON independiente que alguien tiene que recordar revisar.
Para que Wazuh analice y genere alertas sobre estos hallazgos, copia las reglas de ejemplo de examples/wazuh/ en tu gestor de Wazuh, y añade el bloque <localfile> de examples/wazuh/ossec_localfile_snippet.xml al /var/ossec/etc/ossec.conf del agente. No se necesita instalar ningún decodificador personalizado: el localfile está configurado con log_format json, por lo que el decodificador JSON integrado de Wazuh analiza cada línea y mapea cada clave JSON de nivel superior k a data.k, con lo que local_rules.xml coincide directamente.
examples/wazuh/local_rules.xml → /var/ossec/etc/rules/ del gestor<localfile> de examples/wazuh/ossec_localfile_snippet.xml → /var/ossec/etc/ossec.conf del agentesystemctl restart wazuh-manager (gestor), systemctl restart wazuh-agent (host del agente)Estas son solo plantillas de ejemplo, proporcionadas como punto de partida — no han sido probadas contra un gestor de Wazuh en vivo y deberían verificarse en un entorno que no sea de producción antes de confiar en ellas.
Esquema JSON por línea:
| Campo | Tipo | Descripción |
|---|---|---|
timestamp | string (ISO 8601) | Cuándo se reenvió el hallazgo |
hostname | string | El host escaneado |
rule_id | string | Coincide con el ID de regla de detección de ubuntils (ver la tabla de Reglas de Detección arriba) |
severity | string | HIGH | MEDIUM | LOW |
title | string | Título corto legible por humanos |
description | string | Descripción completa del hallazgo |
artifact_path | string | Ruta del archivo/recurso donde se encontró el problema |
raw_value | string | La línea/valor sin procesar que activó la regla |
remediation_available | bool | Si ubuntils tiene un remediador para esta regla |
related_events | array (opcional) | Eventos de línea temporal correlacionados, si los hay |
| Recolector | Artefactos recopilados |
|---|---|
| ProcessCollector | Procesos en ejecución desde /proc y la salida de ps |
| NetworkCollector | Conexiones abiertas y listeners desde ss/netstat |
| UserCollector | /etc/passwd, /etc/shadow, /etc/group |
| CronCollector | Directorios /etc/cron* y /var/spool/cron/crontabs/* |
| SystemdCollector | Salida de systemctl list-timers y list-units |
| SSHCollector | ~/.ssh/authorized_keys para todos los usuarios |
| SudoersCollector | /etc/sudoers y todos los archivos bajo /etc/sudoers.d/ |
| EnvironmentCollector | /etc/environment, /etc/profile.d/*, archivos de inicio del shell de usuario |
| PackageCollector | Integridad de paquetes del sistema vía dpkg --verify, atributos de flag inmutable vía lsattr, y binarios setuid/setgid vía find |
| PamCollector | Archivos /etc/pam.d/* y /etc/nsswitch.conf |
| KernelCollector | Módulos del kernel cargados vía lsmod |
PackageCollector requiere tres herramientas estándar de Ubuntu en el host en vivo (dpkg, lsattr, find — todas presentes en instalaciones estándar de Ubuntu). Si algún comando no está disponible, PackageCollector produce grácilmente datos vacíos para esa porción en lugar de fallar. El análisis offline (analyze BUNDLE) reproduce la salida de comandos capturada en el momento de collect, por lo que no se requiere la disponibilidad de comandos en el host del analizador.
El escaneo find de setuid/setgid pasa -xdev para mantenerse acotado — no desciende a sistemas de archivos montados por separado (un montaje distinto bajo /opt, un /home montado por NFS, etc.). Esto es un compromiso deliberado entre tiempo de ejecución y cobertura: sin -xdev, el escaneo podría colgarse escaneando montajes de red o sistemas de archivos virtuales. Si tu entorno monta estas rutas en sistemas de archivos separados, ten en cuenta que no serán escaneadas.
dpkg --verify y el escaneo find de setuid/setgid usan tiempos de espera generosos no predeterminados (10 minutos y 5 minutos respectivamente, ver ubuntils/collectors/packages.py) ya que ambos pueden legítimamente ejecutarse bastante más allá del valor predeterminado de la biblioteca de 30 segundos en un host real con una base de datos de paquetes o árbol de archivos grande; ubuntils collect usa los mismos tiempos de espera al capturar estos comandos en un bundle.
| Soportado | |
|---|---|
| Ubuntu | 20.04, 22.04, 24.04 |
| Arquitectura | amd64, arm64 |
| Python | 3.9+ |
| Privilegios | Se requiere root para acceso completo a los artefactos |
Ejecutar sin root produce un escaneo parcial con advertencias. Rutas críticas como /etc/shadow, directorios de crontab protegidos, y algunas entradas de /proc serán omitidas.
v1.0.0
v1.1.0
--config)--output FILE para escribir informes directamente--sincereport_sha256, hostname, timestamp)USER_UID_ZEROv1.5.0
--rules)PROCESS_SUSPICIOUS_CONNECTION — correlación proceso↔red por PIDrelated_events)Las búsquedas de hash en VirusTotal y la exportación de IOC a MISP se descartaron de esta versión. VirusTotal solo responde para hashes conocidos — el caso que rkhunter ya cubre, y lo opuesto a la brecha de técnicas novedosas que ubuntils apunta — y ambas características habrían puesto una llamada de red dentro de una herramienta cuyo valor se basa en no hacer ninguna. ubuntils en sí mismo sigue sin hacer llamadas de red (ver la integración con Wazuh para la única forma opcional de que los hallazgos salgan del host, vía un agente local).
v2.0.0 — división offline collect/analyze
ubuntils collect — adquiere un bundle a prueba de manipulación (manifest.json + archivos/comandos con hash) de un host en vivo, sin detecciónubuntils analyze (BUNDLE | --root PATH) — ejecuta la misma canalización de detección/línea temporal que scan contra un bundle o una imagen montada, sin requerir rootbundle_integrity (live/ok/mismatch) expuesto en scan_metadataPROCESS_MASQUERADE, PROCESS_SUSPICIOUS_CONNECTION, y cobertura reducida para rutas glob de cron/sudoers/SSH y ExecStart de temporizadores systemd)confidence/confidence_band/signals), supresión con --baseline, reemplazando heurísticas basadas solo en mtime en SSH_UNAUTHORIZED_KEY/SHELL_RC_MODIFICATION con señales de ctime + contenido, y una línea temporal offline real tanto para analyze BUNDLE como para analyze --rootPACKAGE_TAMPERED, IMMUTABLE_FLAG_SET, PAM_BACKDOOR, KERNEL_MODULE_SUSPICIOUS, SETUID_INVENTORY (vía PackageCollector, PamCollector, KernelCollector; todos solo de marcado por diseño)v2.1.0 — reenvío a SIEM
ubuntils scan en vivo reenviados a un agente local de Wazuh como JSONL, autodetectado (sin flag requerido)<localfile> de ossec.conf (examples/wazuh/)scan en vivo — nunca se activa durante analyze offline, ya que un bundle o imagen describe un host diferente al que ejecuta el agente--no-wazuh para excluir un escaneo del reenvíov2.1.0 — endurecimiento (auditoría completa del código base)
PATH del llamador, y los comandos se resuelven en una ruta segura fija; las ediciones de sudoers se verifican con visudo antes de tocar el archivo; escrituras de remediación atómicas; salida de informes/bundles segura frente a enlaces simbólicos; bundles extraídos eliminados después de analyze/etc/ld.so.preload analizado, entradas de cron @reboot/@daily y scripts de /etc/cron.{hourly,daily,weekly,monthly}, unidades .service de systemd y binarios ExecStart no propiedad de root, reglas sudoers de %group y #include/@includedir, cada elemento de una lista LD_PRELOADUSER_EMPTY_PASSWORD (cuenta de inicio de sesión sin contraseña)/usr/lib tratados como estándar, KERNEL_MODULE_SUSPICIOUS degradado a LOW, un hallazgo NSS por móduloscan_metadata y en la TUI; un fallo de línea temporal ya no descarta hallazgos; bundles manipulados advierten y salen con código 3; año/zona horaria de syslog manejados correctamente--min-confidence (predeterminado 40), resultados mostrados en la TUI después de scan --remediatev3.0.0 / v4.0.0 (exploratorio)
Las contribuciones más útiles en este momento son nuevas reglas de detección (añadidas como funciones independientes en detectors/rules.py con una prueba correspondiente), recolectores adicionales para tipos de artefactos aún no cubiertos, módulos de remediación para SUSPICIOUS_SYSTEMD_TIMER y SHELL_RC_MODIFICATION (ambos actualmente solo de marcado por diseño, pero pueden existir rutas de auto-remediación seguras), casos de prueba para casos límite en configuraciones específicas de Ubuntu, y mejoras en la documentación.
Abre un issue antes de comenzar una contribución grande para evitar trabajo duplicado.
MIT
Creado por Asmit — BTech en Ciencias de la Computación, PES University, Bengaluru. La herramienta surgió de la frustración con lo mucho que tarda el triaje manual de Ubuntu en comparación con lo que un script bien delimitado puede automatizar.
| SHELL_RC_MODIFICATION | LOW | No | Archivos de inicio de shell (bashrc, profile, zshrc, etc.) modificados en las últimas 48 horas para cualquier usuario con shell de inicio de sesión |
| PACKAGE_TAMPERED | HIGH | No | Archivos de paquetes propiedad del sistema modificados, ausentes, o cuyo contenido/modo/tamaño no coincide con el manifiesto del paquete (mediante dpkg --verify) |
| IMMUTABLE_FLAG_SET | MEDIUM | No | Indicadores inmutable (i) o solo anexar (a) establecidos en archivos sensibles como /etc/passwd, /etc/sudoers o /etc/pam.d/* (detectado mediante lsattr) |
| PAM_BACKDOOR | HIGH | No | Una línea literal pam_permit.so en cualquier archivo /etc/pam.d/*, o un módulo NSS en /etc/nsswitch.conf fuera de una lista de permitidos (files/sss/ldap/winbind/...) |
| KERNEL_MODULE_SUSPICIOUS | LOW | No | Módulos del kernel cargados fuera de una lista de permitidos de módulos integrados comunes — nota: los hosts con mucho hardware (GPU, tarjetas Wi-Fi, controladores propietarios) verán falsos positivos; añada los módulos esperados mediante --config |
| SETUID_INVENTORY | LOW | No | Binarios setuid o setgid inesperados fuera de un conjunto de referencia conocido (los dos bits se comprueban y notifican por separado) |