Si llevas tiempo en Linux haciendo las mismas cosas a mano —lanzar escaneos, rotar logs, ejecutar backups, revisar servicios— estás perdiendo tiempo que podrías invertir en aprender algo que realmente importe. La automatización no es solo comodidad: en hacking ético es la diferencia entre un reconocimiento manual que tarda horas y un pipeline que corre solo mientras duermes.
Este artículo es para quien ya sabe moverse en terminal pero todavía no ha conectado los puntos entre bash, cron, ansible y el resto del ecosistema. No voy a listarte herramientas sin contexto. Voy a mostrarte cómo las uso en escenarios reales —reconocimiento, administración de labs, automatización de reportes— con los outputs que obtengo y los errores que me costaron tiempo.
Qué necesitas antes de empezar:
- Linux funcional (Kali, Parrot, Ubuntu, lo que uses)
- Acceso a terminal con permisos de usuario normal y sudo cuando sea necesario
- Conocimiento básico de comandos:
ls,cd,cat,grep,chmod - Ganas de dejar de hacer lo mismo dos veces

Por Qué la Automatización es Crítica en Linux (y en Hacking Ético)
Cuando empecé en CTFs, ejecutaba nmap a mano cada vez. Luego copiaba los puertos, lanzaba otro escaneo de versiones, abría el navegador para el servicio web, todo manual. En una máquina de HackTheBox eso son 15 minutos de trabajo mecánico antes de siquiera pensar en vulnerabilidades.
La automatización en Linux no es un lujo de sysadmin. Es una habilidad ofensiva. Los mejores scripts de reconocimiento que existen —AutoRecon, reconftw— son exactamente esto: bash y Python pegando herramientas que ya conoces. Cuando entiendes cómo funcionan por dentro, puedes modificarlos, depurarlos y construir los tuyos.
Desde el lado defensivo, un servidor sin tareas programadas para rotar logs, verificar integridad de archivos o limpiar temporales es un servidor que eventualmente explota por disco lleno o que nadie nota cuando algo cambia. He visto máquinas en CTFs donde el vector de entrada era un script mal configurado que corría como root. Conocer estas herramientas también te enseña a encontrar ese tipo de configuraciones erróneas.
Bash y Shell Scripting: La Base de Todo
Bash es el pegamento de Linux. Antes de tocar cualquier herramienta de automatización, necesitas entender que todo lo demás —cron, ansible, systemd timers— en algún punto ejecuta comandos de shell.
Un script básico de reconocimiento que uso como punto de partida en labs:
#!/bin/bash
# recon_basic.sh — reconocimiento inicial para CTFs/labs autorizados
# Uso: ./recon_basic.sh <IP_OBJETIVO>
TARGET=$1
OUTPUT_DIR="./recon_$TARGET"
if [ -z "$TARGET" ]; then
echo "[!] Uso: $0 <IP>"
exit 1
fi
mkdir -p "$OUTPUT_DIR"
echo "[*] Iniciando reconocimiento en $TARGET"
echo "[*] Resultados en: $OUTPUT_DIR"
# Ping check
if ping -c 1 "$TARGET" &>/dev/null; then
echo "[+] Host activo"
else
echo "[-] Host no responde a ping (puede seguir activo)"
fi
# Escaneo rápido de puertos
echo "[*] Escaneo rápido de puertos..."
nmap -T4 --open -p- "$TARGET" -oN "$OUTPUT_DIR/ports_fast.txt" 2>/dev/null
# Extraer puertos abiertos y lanzar escaneo de versiones
PORTS=$(grep "^[0-9]" "$OUTPUT_DIR/ports_fast.txt" | cut -d'/' -f1 | tr '\n' ',' | sed 's/,$//')
if [ -n "$PORTS" ]; then
echo "[+] Puertos encontrados: $PORTS"
echo "[*] Escaneo de versiones y scripts..."
nmap -sV -sC -p "$PORTS" "$TARGET" -oN "$OUTPUT_DIR/services.txt" 2>/dev/null
echo "[+] Completado. Revisa $OUTPUT_DIR/"
else
echo "[-] No se encontraron puertos abiertos"
fiLo que hace este script es lo que harías a mano, pero encadenado. El truco está en extraer los puertos del primer escaneo y pasarlos al segundo: así no pierdes tiempo escaneando versiones en los 65535 puertos.
Estructura fundamental que debes dominar:
# Variables
NOMBRE="valor"
echo $NOMBRE
# Condicionales
if [ -f "/etc/passwd" ]; then
echo "archivo existe"
fi
# Bucles
for IP in 192.168.1.{1..254}; do
ping -c 1 -W 1 $IP &>/dev/null && echo "$IP activo"
done
# Funciones
check_port() {
nc -zv "$1" "$2" 2>&1 | grep -q "succeeded" && echo "Puerto $2 abierto en $1"
}
check_port 10.10.10.1 80
# Redirección de errores
comando 2>/dev/null # suprimir errores
comando > salida.txt 2>&1 # todo a archivo
comando &>/dev/null # todo al vacíoUn error que cometí al principio: no validar inputs. Un script que recibe una IP y no verifica el formato puede hacer cosas raras si le pasas un string vacío o un nombre de host con caracteres especiales. Siempre valida lo que entra.
Cron Jobs: Programar Tareas en Linux sin Excusas
Cron es el scheduler por defecto en prácticamente cualquier distribución Linux. La sintaxis intimida al principio pero se aprende en 10 minutos.
# Formato: minuto hora día_mes mes día_semana comando
# * * * * * comando
# Ejemplos:
0 2 * * * /usr/bin/backup.sh # cada día a las 2:00 AM
*/15 * * * * /usr/bin/check_services.sh # cada 15 minutos
0 9 * * 1 /usr/bin/weekly_report.sh # lunes a las 9:00 AM
0 */6 * * * /usr/bin/sync_logs.sh # cada 6 horasPara editar tu crontab:
crontab -e # editar crontab del usuario actual
crontab -l # listar tareas programadas
crontab -r # eliminar todo el crontab (cuidado)
sudo crontab -e # crontab de rootOutput real de crontab -l en una de mis máquinas de lab:
# Crontab del usuario pentester — lab local
*/5 * * * * /home/pentester/scripts/check_vpn.sh >> /tmp/vpn_status.log 2>&1
0 3 * * * /home/pentester/scripts/backup_notes.sh
0 * * * * /usr/bin/find /tmp -name "*.tmp" -mtime +1 -delete
30 8 * * 1-5 /home/pentester/scripts/daily_summary.sh | mail -s "Lab Summary" pentester@localLa advertencia que casi nadie menciona: cuando cron ejecuta un script, el PATH no es el mismo que tienes en tu sesión interactiva. Cron usa un PATH mínimo: /usr/bin:/bin. Si tu script llama a nmap, python3, o cualquier herramienta que está en /usr/local/bin o en un virtualenv, va a fallar silenciosamente.
La solución es siempre usar rutas absolutas o definir el PATH al inicio del script:
#!/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# resto del script...Esto me costó dos horas una vez que un script de backup «funcionaba» cuando lo ejecutaba a mano pero nunca corría bien desde cron. El log estaba vacío porque el script moría en el primer comando que no encontraba.
Scripts Shell Avanzados: Más Allá del Básico
Una vez que dominas la estructura básica, el siguiente nivel es hacer scripts que sean reutilizables y que manejen errores correctamente.
#!/bin/bash
# monitor_web.sh — monitoreo de disponibilidad HTTP
# Útil para labs: verificar si un servicio web sigue activo durante un pentest
set -euo pipefail # exit on error, undefined vars, pipe failures
TARGETS_FILE="${1:-targets.txt}"
LOG_FILE="/tmp/web_monitor_$(date +%Y%m%d).log"
TIMEOUT=5
log() {
echo "[$(date '+%H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
check_http() {
local url="$1"
local code
code=$(curl -s -o /dev/null -w "%{http_code}" --max-time "$TIMEOUT" "$url" 2>/dev/null)
case "$code" in
200|301|302) log "[+] $url — HTTP $code" ;;
000) log "[-] $url — Sin respuesta (timeout o down)" ;;
*) log "[!] $url — HTTP $code (inesperado)" ;;
esac
}
if [ ! -f "$TARGETS_FILE" ]; then
log "[!] Archivo $TARGETS_FILE no encontrado"
exit 1
fi
log "[*] Iniciando monitoreo desde $TARGETS_FILE"
while IFS= read -r target || [ -n "$target" ]; do
[[ "$target" =~ ^#|^$ ]] && continue # ignorar comentarios y líneas vacías
check_http "$target"
done < "$TARGETS_FILE"
log "[*] Completado. Log: $LOG_FILE"El set -euo pipefail al inicio es algo que aprendí tarde. Sin eso, un script puede continuar ejecutándose después de que un comando falla, y terminas con resultados parciales que parecen completos. En pentesting eso es peligroso: podrías pensar que un escaneo terminó cuando en realidad murió a mitad.
Ansible: Automatización a Escala
Ansible es otra categoría. Mientras bash es para automatizar tareas en una máquina, Ansible es para automatizar tareas en muchas máquinas simultáneamente, sin agente, usando SSH.
En hacking ético, Ansible tiene dos usos principales:
- Gestión de laboratorios: configurar múltiples VMs con las mismas herramientas, usuarios, configuraciones
- Post-explotación documentada: cuando tienes acceso a múltiples sistemas en un pentest de red interna, Ansible puede ayudarte a ejecutar comandos de recolección de información de forma ordenada (en entornos autorizados)
Instalación:
sudo apt install ansible -y
# o con pip
pip3 install ansibleEstructura básica de un playbook para configurar un lab de pentesting:
# setup_pentest_lab.yml
---
- name: Configurar herramientas básicas de pentesting
hosts: lab_vms
become: yes
vars:
tools:
- nmap
- gobuster
- nikto
- sqlmap
- python3-pip
- curl
- wget
- netcat-openbsd
tasks:
- name: Actualizar repositorios
apt:
update_cache: yes
cache_valid_time: 3600
- name: Instalar herramientas
apt:
name: "{{ tools }}"
state: present
- name: Crear directorio de trabajo
file:
path: /opt/pentest
state: directory
mode: '0755'
owner: "{{ ansible_user }}"
- name: Clonar wordlists básicas
git:
repo: https://github.com/danielmiessler/SecLists.git
dest: /opt/seclists
depth: 1
ignore_errors: yesInventario simple (hosts.ini):
[lab_vms]
192.168.56.10 ansible_user=kali ansible_ssh_private_key_file=~/.ssh/lab_key
192.168.56.11 ansible_user=kali ansible_ssh_private_key_file=~/.ssh/lab_key
[lab_vms:vars]
ansible_python_interpreter=/usr/bin/python3Ejecutar:
ansible-playbook -i hosts.ini setup_pentest_lab.ymlOutput real de una ejecución:
PLAY [Configurar herramientas básicas de pentesting] ***************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.56.10]
ok: [192.168.56.11]
TASK [Actualizar repositorios] *************************************************
changed: [192.168.56.10]
changed: [192.168.56.11]
TASK [Instalar herramientas] ***************************************************
changed: [192.168.56.10]
changed: [192.168.56.11]
TASK [Crear directorio de trabajo] *********************************************
changed: [192.168.56.10]
changed: [192.168.56.11]
TASK [Clonar wordlists básicas] ************************************************
changed: [192.168.56.10]
changed: [192.168.56.11]
PLAY RECAP *********************************************************************
192.168.56.10 : ok=5 changed=4 unreachable=0 failed=0 skipped=0
192.168.56.11 : ok=5 changed=4 unreachable=0 failed=0 skipped=0Lo que me gusta de Ansible sobre un script bash que hace SSH en bucle: la idempotencia. Si corres el playbook dos veces, no instala las herramientas dos veces ni rompe nada. Un script bash con apt install en bucle puede hacer cosas raras si algo ya está parcialmente instalado.
Systemd Timers: La Alternativa Moderna a Cron
Si usas una distro moderna (Ubuntu 20.04+, Debian 11+, cualquier Arch), systemd timers son una alternativa a cron con mejor logging y más control.
La ventaja real sobre cron: puedes ver exactamente cuándo corrió una tarea y qué salida produjo con journalctl. Con cron, si no configuras logging explícito, los errores desaparecen.
Crear un timer para ejecutar un script cada hora:
# 1. Crear el servicio
sudo nano /etc/systemd/system/mi-script.service[Unit]
Description=Mi script de automatización
After=network.target
[Service]
Type=oneshot
User=pentester
ExecStart=/home/pentester/scripts/mi_script.sh
StandardOutput=journal
StandardError=journal# 2. Crear el timer
sudo nano /etc/systemd/system/mi-script.timer[Unit]
Description=Ejecutar mi-script cada hora
[Timer]
OnBootSec=5min
OnUnitActiveSec=1h
Persistent=true
[Install]
WantedBy=timers.target# 3. Activar y arrancar
sudo systemctl daemon-reload
sudo systemctl enable mi-script.timer
sudo systemctl start mi-script.timer
# Ver estado
systemctl status mi-script.timer
systemctl list-timers --all
# Ver logs
journalctl -u mi-script.service -fPersistent=true es importante: si el sistema estaba apagado cuando debía correr la tarea, la ejecuta al arrancar. Cron no hace esto por defecto.
Make y Taskfile: Automatización de Workflows
make no es solo para compilar código. Es un runner de tareas con dependencias. Lo uso para organizar workflows de pentesting donde hay pasos que dependen de otros.
# Makefile para workflow de reconocimiento
# Uso: make recon TARGET=10.10.10.1
TARGET ?= 10.10.10.1
OUTPUT := ./output/$(TARGET)
.PHONY: all recon ports services web clean
all: recon
$(OUTPUT):
mkdir -p $(OUTPUT)
recon: $(OUTPUT) ports services
@echo "[+] Reconocimiento completo para $(TARGET)"
ports: $(OUTPUT)
@echo "[*] Escaneo de puertos..."
nmap -T4 --open -p- $(TARGET) -oN $(OUTPUT)/ports.txt
services: ports
@echo "[*] Escaneo de servicios..."
$(eval PORTS := $(shell grep "^[0-9]" $(OUTPUT)/ports.txt | cut -d'/' -f1 | tr '\n' ',' | sed 's/,$$//'))
nmap -sV -sC -p $(PORTS) $(TARGET) -oN $(OUTPUT)/services.txt
web: $(OUTPUT)
@echo "[*] Enumeración web..."
gobuster dir -u http://$(TARGET) -w /usr/share/wordlists/dirb/common.txt \
-o $(OUTPUT)/gobuster.txt 2>/dev/null
clean:
rm -rf ./output/$(TARGET)Ejecutar:
make recon TARGET=10.10.10.5
make web TARGET=10.10.10.5
make clean TARGET=10.10.10.5La dependencia services: ports garantiza que el escaneo de servicios nunca corre antes de tener los puertos. Si ports falla, services no se ejecuta. Con un script bash lineal, tendrías que manejar esto manualmente.
Python para Automatización: Cuando Bash No Alcanza
Bash es perfecto para encadenar comandos del sistema. Cuando necesitas lógica más compleja —parsear JSON, hacer requests HTTP, manipular datos estructurados— Python es la herramienta correcta.
Un script que uso para parsear resultados de nmap y generar un resumen:
#!/usr/bin/env python3
# parse_nmap.py — parsear XML de nmap y generar resumen
# Requiere: pip3 install python-libnmap
from libnmap.parser import NmapParser
import sys
import json
from datetime import datetime
def parse_nmap_xml(xml_file):
try:
report = NmapParser.parse_fromfile(xml_file)
except Exception as e:
print(f"[!] Error parseando {xml_file}: {e}")
sys.exit(1)
results = {
"scan_date": datetime.now().isoformat(),
"hosts": []
}
for host in report.hosts:
if host.status != "up":
continue
host_data = {
"ip": host.address,
"hostname": host.hostnames[0] if host.hostnames else "N/A",
"os": host.os_fingerprint if host.os_fingerprint else "Unknown",
"services": []
}
for service in host.services:
if service.state == "open":
host_data["services"].append({
"port": service.port,
"protocol": service.protocol,
"service": service.service,
"version": service.banner
})
results["hosts"].append(host_data)
return results
if __name__ == "__main__":
if len(sys.argv) != 2:
print(f"Uso: {sys.argv[0]} <nmap_output.xml>")
sys.exit(1)
data = parse_nmap_xml(sys.argv[1])
# Mostrar resumen
print(f"\n[+] Hosts activos: {len(data['hosts'])}")
for host in data["hosts"]:
print(f"\n IP: {host['ip']} ({host['hostname']})")
print(f" OS: {host['os']}")
print(f" Puertos abiertos: {len(host['services'])}")
for svc in host['services']:
print(f" {svc['port']}/{svc['protocol']} — {svc['service']} {svc['version']}")
# Guardar JSON
output_file = sys.argv[1].replace('.xml', '_parsed.json')
with open(output_file, 'w') as f:
json.dump(data, f, indent=2)
print(f"\n[+] JSON guardado: {output_file}")Para usar nmap con salida XML:
nmap -sV -sC -p 22,80,443,8080 10.10.10.1 -oX scan.xml
python3 parse_nmap.py scan.xmlIntegrar Todo: Un Pipeline Real de Reconocimiento
Aquí está cómo combino estas herramientas en un flujo real. Este es el pipeline que uso cuando empiezo una máquina nueva en HackTheBox:
#!/bin/bash
# pipeline_recon.sh — pipeline completo de reconocimiento inicial
# Tiempo estimado: 5-20 minutos dependiendo del objetivo
# Solo para uso en entornos autorizados: HTB, THM, labs propios
set -uo pipefail
TARGET="${1:?'Uso: $0 <IP>'}"
WORKDIR="$HOME/htb/$(date +%Y%m%d)_$TARGET"
WORDLIST="/usr/share/wordlists/dirb/common.txt"
mkdir -p "$WORKDIR"/{nmap,web,notes}
exec > >(tee "$WORKDIR/notes/pipeline.log") 2>&1
timestamp() { date '+%H:%M:%S'; }
log_info() { echo "[$(timestamp)] [*] $1"; }
log_ok() { echo "[$(timestamp)] [+] $1"; }
log_warn() { echo "[$(timestamp)] [!] $1"; }
log_info "Target: $TARGET"
log_info "Workdir: $WORKDIR"
# Fase 1: Descubrimiento de puertos (rápido)
log_info "Fase 1: Descubrimiento de puertos"
nmap -T4 --open -p- --min-rate 5000 "$TARGET" \
-oN "$WORKDIR/nmap/allports.txt" \
-oX "$WORKDIR/nmap/allports.xml" 2>/dev/null
OPEN_PORTS=$(grep "^[0-9]" "$WORKDIR/nmap/allports.txt" | \
grep "open" | cut -d'/' -f1 | tr '\n' ',' | sed 's/,$//')
if [ -z "$OPEN_PORTS" ]; then
log_warn "No se encontraron puertos abiertos. Verifica conectividad."
exit 1
fi
log_ok "Puertos abiertos: $OPEN_PORTS"
# Fase 2: Servicios y scripts
log_info "Fase 2: Enumeración de servicios"
nmap -sV -sC -p "$OPEN_PORTS" "$TARGET" \
-oN "$WORKDIR/nmap/services.txt" 2>/dev/null
log_ok "Servicios enumerados"
# Fase 3: Si hay HTTP/HTTPS, enumerar directorios
for PORT in $(echo "$OPEN_PORTS" | tr ',' '\n'); do
case "$PORT" in
80|8080|8000)
log_info "Puerto web detectado: $PORT — lanzando gobuster"
gobuster dir -u "http://$TARGET:$PORT" \
-w "$WORDLIST" \
-o "$WORKDIR/web/gobuster_$PORT.txt" \
-q 2>/dev/null &
;;
443|8443)
log_info "Puerto HTTPS detectado: $PORT — lanzando gobuster"
gobuster dir -u "https://$TARGET:$PORT" \
-w "$WORDLIST" \
-k \
-o "$WORKDIR/web/gobuster_$PORT.txt" \
-q 2>/dev/null &
;;
esac
done
wait # esperar todos los gobuster en background
log_ok "Pipeline completado. Resultados en: $WORKDIR"El & al final de los gobuster los lanza en background y el wait al final espera a que todos terminen antes de continuar. Así los escaneos web corren en paralelo en vez de secuencial.
Errores Comunes y Cómo Evitarlos
1. Scripts sin manejo de errores que fallan silenciosamente
El error más frecuente: un script que corre desde cron, falla en el paso 2, y continúa ejecutando los pasos 3, 4 y 5 con datos incorrectos o sin datos. El output parece normal pero los resultados son basura.
Solución: set -euo pipefail al inicio de cada script bash. -e detiene el script si cualquier comando falla. -u trata variables no definidas como error. -o pipefail hace que un pipe falle si cualquier comando en él falla.
#!/bin/bash
set -euo pipefail
# Ahora si algo falla, el script para y puedes saber exactamente dónde2. Cron jobs que funcionan a mano pero no en cron
Ya lo mencioné con el PATH, pero hay otro problema frecuente: rutas relativas. Si tu script hace cd /home/user/scripts && ./helper.sh, eso funciona en tu sesión porque el directorio existe. Cron ejecuta desde /, así que las rutas relativas se resuelven desde ahí.
Siempre usa rutas absolutas o añade esto al inicio:
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"3. Ansible fallando por versiones de Python inconsistentes
En labs con múltiples VMs, es común tener mezcla de Ubuntu 20.04 (Python 3.8) y 22.04 (Python 3.10). Ansible puede conectar pero fallar en módulos específicos si no especificas el intérprete.
Solución en el inventario:
[lab_vms:vars] ansible_python_interpreter=/usr/bin/python3O en el playbook:
- hosts: all
vars:
ansible_python_interpreter: /usr/bin/python34. Permisos incorrectos en scripts de cron
Un script que no tiene permisos de ejecución falla desde cron con un error críptico. Siempre:
chmod +x /ruta/al/script.sh
# Y verifica que el usuario de cron tiene acceso al directorio
ls -la /ruta/al/script.sh5. No redirigir salida en cron y llenar el mailbox del sistema
Por defecto, cron envía la salida de cada tarea al mail del usuario. En muchos sistemas esto llena /var/spool/mail hasta que el disco se llena. Siempre redirige:
*/5 * * * * /usr/bin/mi_script.sh >> /var/log/mi_script.log 2>&1Próximos Pasos
Si este artículo te abrió el apetito, estos son los caminos naturales para seguir:
1. Profundizar en bash scripting para pentesting
El siguiente nivel es aprender a procesar texto con awk, sed y grep de forma avanzada. La mayoría de outputs de herramientas de seguridad son texto plano y saber manipularlo te ahorra instalar dependencias innecesarias.
→ Recurso: Bash Scripting para Hacking Ético en hackchest.net
2. Automatización de reconocimiento con Python
Una vez que dominas bash, Python te da acceso a APIs, parseo de XML/JSON, y librerías como scapy para manipulación de paquetes. El salto natural es escribir tus propias herramientas de reconocimiento.
→ Recurso: Python para Pentesting: Primeros Scripts en hackchest.net
3. Privilege Escalation a través de cron y scripts mal configurados
Conocer cómo funcionan estas herramientas te enseña a encontrar configuraciones erróneas. Un cron job que ejecuta un script escribible por usuarios sin privilegios, o un script que usa rutas relativas en un directorio donde puedes escribir, son vectores reales de escalada de privilegios.
→ Recurso: Linux Privilege Escalation: Cron y Wildcards en hackchest.net
FAQ
¿Cuánto tiempo lleva aprender a automatizar tareas en Linux de forma útil?
Con dedicación real —una hora diaria practicando— en 3-4 semanas puedes escribir scripts bash funcionales que resuelvan problemas concretos. El primer mes no vas a escribir nada elegante, pero vas a escribir cosas que funcionan. Ansible toma un poco más porque implica entender el modelo declarativo, pero un playbook básico lo puedes tener en un día.
El pipeline completo de este artículo —bash + cron + Python básico— lo puedes tener operativo en un fin de semana si te enfocas. Lo que toma tiempo es el refinamiento: manejar errores correctamente, hacer scripts reutilizables, no repetirte.
¿Es mejor usar cron jobs o systemd timers para tareas programadas en Linux?
Depende del contexto. Si estás en un sistema moderno con systemd (la mayoría de distros actuales), los timers son objetivamente superiores: mejor logging, más opciones de scheduling, integración con el sistema de servicios. El comando journalctl -u mi-servicio.service te da todo el historial de ejecuciones sin configurar nada extra.
Cron sigue siendo válido para scripts simples, para compatibilidad con sistemas más antiguos, o cuando necesitas algo que funcione en cualquier Linux sin pensar. Para un lab de pentesting personal, cron es perfectamente suficiente.
¿Puedo usar estas técnicas de automatización en un pentest real?
Sí, con matices importantes. Ansible para gestionar tu infraestructura de ataque (C2, redirectores, herramientas) es completamente estándar. Scripts bash para automatizar reconocimiento también. Lo que debes tener cuidado es con la velocidad y el ruido: un script que lanza nmap con --min-rate 10000 en

