Laboratorio SIEM Casero: Wazuh en Raspberry Pi 5
Guía completa para montar tu propio centro de operaciones de seguridad (SOC) en casa con Wazuh sobre Raspberry Pi 5, incluyendo simulación de ataques y detección con MITRE ATT&CK.
Documentación del Proyecto: Laboratorio SIEM Casero con Wazuh en Raspberry Pi 5
Autor: Maximiliano Barcia
Fecha: 09-03-2026
Versión: 2.0 (Completa y Replicable)
1. Resumen Ejecutivo
Este documento describe la implementación de un laboratorio de seguridad (SIEM) en un entorno doméstico utilizando una Raspberry Pi 5 como servidor central y una máquina virtual (CTF-Labs) como objetivo de pruebas.
El objetivo principal era centralizar y analizar logs de seguridad, simulando ataques para comprender el flujo de trabajo de un analista de SOC (Security Operations Center).
El sistema se basa en Wazuh, una plataforma de seguridad de código abierto. El proyecto incluye la resolución de problemas críticos de compatibilidad ARM64 y de permisos en almacenamiento externo.
2. Objetivos del Proyecto
- Diseñar e implementar un SIEM funcional en un entorno optimizado con bajo presupuesto.
- Centralizar la recolección de logs desde un agente remoto.
- Generar eventos de seguridad controlados (ataques simulados) y verificar su detección.
- Aprender a navegar e investigar alertas en la interfaz de Wazuh/Kibana.
- Documentar todo el proceso para futuras referencias y aprendizaje.
3. Arquitectura de la Red e Infraestructura
La infraestructura consta de los siguientes componentes principales:
| Componente | Hardware/SO | IP/Dirección | Rol Principal |
|---|---|---|---|
| Servidor Wazuh (Manager) | Raspberry Pi 5 (64GB SD + 2TB Disco Externo) con Raspberry Pi OS | 192.168.0.200:8443 | Centraliza logs, gestiona agentes, panel web Kibana. |
| Máquina Objetivo (Agente) | Máquina Virtual (CTF-Labs - KALI) | 192.168.0.21 | Ejecuta el agente Wazuh y contenedores vulnerables. |
| Máquina Atacante | Máquina Virtual (Nyx - Kali Linux) | 192.168.0.27 | Punto desde donde se lanzan los ataques simulados. |
| Contenedor Vulnerable | Docker en CTF-Labs (Imagen “escolares”) | 172.17.0.2 (Docker) | Contenedor con WordPress/Apache que actúa como víctima. |
Flujo de Datos:
Los ataques desde Nyx hacia el contenedor en CTF-Labs generan logs en el contenedor. A través de un volumen de Docker, estos logs se sincronizan con el sistema de archivos del host CTF-Labs en la ruta /home/cft/logs_victima/, donde el agente Wazuh los lee y los envía al Manager en la Raspberry Pi para su análisis y visualización.
4. Configuración e Instalación Detallada
4.1. Configuración de la Raspberry Pi 5 (Servidor Wazuh)
4.1.1. Instalación del Sistema Operativo:
Se instaló Raspberry Pi OS (64-bit) en una tarjeta microSD de 64GB.
Se siguieron las guías de los videos: https://www.youtube.com/watch?v=xRsxs5eBpmI&t https://www.youtube.com/watch?v=-7vvELophxU&t
4.1.2. Instalación de Portainer (Opcional pero Recomendado):
Se instaló Docker y Docker Compose.
Se desplegó Portainer para facilitar la gestión de los contenedores.
4.1.3. Despliegue de Wazuh con Docker Compose:
Se utilizó el repositorio oficial de Wazuh Docker.
Adaptación para ARM64: Se modificó el archivo
docker-compose.ymlpara asegurar la compatibilidad con la arquitecturaarm64de la Raspberry Pi 5 (esto implicó cambiar algunas imágenes a sus versiones compatibles).El stack de Wazuh (Manager, Indexer, Dashboard) se desplegó correctamente.
4.1.4. Generación de Certificados
Utilizamos las herramientas oficiales para generar la infraestructura de clave pública (PKI) necesaria para la comunicación segura entre los componentes. 
Captura: Descarga y generación del script de certificados.
Nota técnica para ARM64: Debido a que el script de generación original estaba compilado para amd64, en algunos pasos tuvimos que intervenir manualmente con OpenSSL para asegurar la compatibilidad con la arquitectura de la Raspberry Pi 5.
4.1.5. Configuración del docker-compose.yml
Docker-Compose
Resolución de Conflictos (Port 443)
Al intentar levantar el stack, nos encontramos con el siguiente error: Error response from daemon: ... Bind for 0.0.0.0:443 failed: port is already allocated
Solución: Cambiamos el puerto del Dashboard de 443 a 8443 para evitar conflictos con Pi-hole o servicios del sistema que ya ocupaban el puerto HTTPS estándar.
4.1.6. Despliegue Final
Una vez ajustados los puertos y las arquitecturas, ejecutamos el despliegue final. A pesar de los avisos de “platform mismatch”, la Raspberry Pi 5 ejecutó los binarios correctamente.
1
nyx-pi@Nyx-Pi:/mnt/datos/wazuh-docker/wazuh-demo1 $ docker compose up -d
Estado de los Contenedores
Verificamos que todos los servicios estuvieran en estado Up (saludable):

Captura: Contenedores de Wazuh corriendo junto a Pi-hole y Portainer.
4.1.7. Acceso al Dashboard
Finalmente, accedimos a la interfaz web a través del puerto configurado (8443).

Captura: Dashboard de Wazuh operativo tras el Health Check inicial.
Detalles Técnicos de Acceso
- URL:
https://192.168.0.200:8443 - Credenciales por defecto:
- Usuario:
admin - Password:
SecretPassword(Configurado en el.yml)
- Usuario:
4.1.8. 🛠️ Configuración de Persistencia en Almacenamiento Externo (SSD/HDD)
Por defecto, los despliegues de Docker Wazuh utilizan volúmenes nombrados, lo que almacena los datos en la partición raíz (tarjeta SD). Para un entorno de producción o laboratorio SOC de larga duración, es imperativo realizar un Bind Mount hacia el disco externo.
Preparación de los Directorios Físicos
1
2
3
4
5
6
# Crear estructura en el disco de 2TB
mkdir -p /mnt/datos/wazuh_data/manager_etc \
/mnt/datos/wazuh_data/manager_logs \
/mnt/datos/wazuh_data/manager_queue \
/mnt/datos/wazuh_data/manager_api \
/mnt/datos/wazuh_data/indexer_data
Nota sobre permisos: Durante el proceso, nos encontramos con que el comando chown no funcionaba correctamente en el disco externo, manteniendo los archivos con propietario nyx-pi. La solución de emergencia fue dar permisos totales:
1
sudo chmod -R 777 /mnt/datos/wazuh_data
Modificación del docker-compose.yml
Se deben sustituir las referencias de volúmenes internos por rutas absolutas. Es crucial identificar correctamente el punto de montaje interno de cada contenedor.
Para Wazuh Indexer (Base de Datos):
1
2
3
4
wazuh.indexer:
...
volumes:
- /mnt/datos/wazuh_data/indexer_data:/usr/share/wazuh-indexer/data
Para Wazuh Manager (Alertas y Logs):
1
2
3
4
5
6
wazuh.manager:
user: "0:0" # Forzado para evitar conflictos de permisos en el disco
volumes:
- /mnt/datos/wazuh_data/manager_logs:/var/ossec/logs
- /mnt/datos/wazuh_data/manager_etc:/var/ossec/etc
Aplicación de Cambios y Limpieza
1
2
3
4
5
6
7
# 1. Detener el stack actual
cd /mnt/datos/wazuh-docker/wazuh-demo1
docker compose down
# 2. Eliminar volúmenes locales antiguos (libera espacio en la SD)
docker volume prune -f
# 3. Levantar con la nueva configuración
docker compose up -d
Verificación de Analista SOC
1
2
# Verificar existencia de alertas por año/mes
sudo ls -R /mnt/datos/wazuh_data/manager_logs/alerts/
- Resultado esperado: Presencia de la carpeta
2026/Mar/ossec-alerts-06.json. - Inspección de Docker: Al ejecutar
docker inspect, la secciónMountsdebe mostrar elSourceapuntando a/mnt/datos/....
4.2. Configuración de la Máquina Objetivo (CTF-Labs - IP .21)
El objetivo era centralizar todos los logs, incluso de aplicaciones dentro de contenedores Docker.
4.2.1. Instalación del Agente Wazuh en el Host
- Se añadió el repositorio de Wazuh y se instaló el paquete
wazuh-agent. - Se configuró el archivo
/var/ossec/etc/ossec.confpara apuntar al manager (<address>192.168.0.200</address>). - Se inició y habilitó el servicio con
systemctl.
1
2
3
4
5
# En CTF-Labs (192.168.0.21)
curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | sudo apt-key add -
echo "deb https://packages.wazuh.com/4.x/apt/ stable main" | sudo tee /etc/apt/sources.list.d/wazuh.list
sudo apt update
sudo apt install wazuh-agent -y
Captura: Instalación del agente Wazuh en la máquina CTF-Labs.
4.2.2. Configuración de Red y Permisos
- Se ajustaron reglas de
iptablespara permitir el tráfico necesario y se configuró el logging para asegurar la trazabilidad. - Se gestionaron permisos de usuarios y carpetas para que el agente Wazuh pudiera leer los logs del sistema y de las aplicaciones.
4.2.3. Estrategia de Recolección de Logs desde Contenedores
- Punto Clave: Para que el agente en el host pudiera ver los logs de los contenedores, se utilizó la funcionalidad de volúmenes de Docker.
- Al desplegar el contenedor vulnerable (con el script de DockerLabs), se montó el volumen
-v /var/log/apache2:/var/log/apache2:rw. Esto sincroniza el directorio de logs de Apache dentro del contenedor con el directorio/var/log/apache2en el hostCTF-Labs. - De esta forma, el agente Wazuh en el host puede monitorear los logs del contenedor como si fueran locales.
4.2.4. Configuración del Agente para Leer Logs El archivo de configuración completo del agente en /var/ossec/etc/ossec.conf quedó así:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
<!--
Wazuh - Agent - Default configuration for kali 2026.1
More info at: https://documentation.wazuh.com
Mailing list: https://groups.google.com/forum/#!forum/wazuh
-->
<ossec_config>
<client>
<server>
<address>192.168.0.200</address>
<port>1514</port>
<protocol>tcp</protocol>
</server>
<config-profile>kali, kali2026, kali2026.1</config-profile>
<notify_time>20</notify_time>
<time-reconnect>60</time-reconnect>
<auto_restart>yes</auto_restart>
<crypto_method>aes</crypto_method>
</client>
<client_buffer>
<disabled>no</disabled>
<queue_size>5000</queue_size>
<events_per_second>500</events_per_second>
</client_buffer>
<!-- Policy monitoring -->
<rootcheck>
<disabled>no</disabled>
<check_files>yes</check_files>
<check_trojans>yes</check_trojans>
<check_dev>yes</check_dev>
<check_sys>yes</check_sys>
<check_pids>yes</check_pids>
<check_ports>yes</check_ports>
<check_if>yes</check_if>
<frequency>43200</frequency>
<rootkit_files>etc/shared/rootkit_files.txt</rootkit_files>
<rootkit_trojans>etc/shared/rootkit_trojans.txt</rootkit_trojans>
<skip_nfs>yes</skip_nfs>
<ignore>/var/lib/containerd</ignore>
<ignore>/var/lib/docker/overlay2</ignore>
</rootcheck>
<wodle name="cis-cat">
<disabled>yes</disabled>
<timeout>1800</timeout>
<interval>1d</interval>
<scan-on-start>yes</scan-on-start>
<java_path>wodles/java</java_path>
<ciscat_path>wodles/ciscat</ciscat_path>
</wodle>
<!-- Osquery integration -->
<wodle name="osquery">
<disabled>yes</disabled>
<run_daemon>yes</run_daemon>
<log_path>/var/log/osquery/osqueryd.results.log</log_path>
<config_path>/etc/osquery/osquery.conf</config_path>
<add_labels>yes</add_labels>
</wodle>
<!-- System inventory -->
<wodle name="syscollector">
<disabled>no</disabled>
<interval>1h</interval>
<scan_on_start>yes</scan_on_start>
<hardware>yes</hardware>
<os>yes</os>
<network>yes</network>
<packages>yes</packages>
<ports all="yes">yes</ports>
<processes>yes</processes>
<users>yes</users>
<groups>yes</groups>
<services>yes</services>
<browser_extensions>yes</browser_extensions>
<synchronization>
<max_eps>10</max_eps>
</synchronization>
</wodle>
<sca>
<enabled>yes</enabled>
<scan_on_start>yes</scan_on_start>
<interval>12h</interval>
<skip_nfs>yes</skip_nfs>
</sca>
<!-- File integrity monitoring -->
<syscheck>
<disabled>no</disabled>
<frequency>43200</frequency>
<scan_on_start>yes</scan_on_start>
<directories>/etc,/usr/bin,/usr/sbin</directories>
<directories>/bin,/sbin,/boot</directories>
<ignore>/etc/mtab</ignore>
<ignore>/etc/hosts.deny</ignore>
<ignore>/etc/mail/statistics</ignore>
<ignore>/etc/random-seed</ignore>
<ignore>/etc/random.seed</ignore>
<ignore>/etc/adjtime</ignore>
<ignore>/etc/httpd/logs</ignore>
<ignore>/etc/utmpx</ignore>
<ignore>/etc/wtmpx</ignore>
<ignore>/etc/cups/certs</ignore>
<ignore>/etc/dumpdates</ignore>
<ignore>/etc/svc/volatile</ignore>
<ignore type="sregex">.log$|.swp$</ignore>
<nodiff>/etc/ssl/private.key</nodiff>
<skip_nfs>yes</skip_nfs>
<skip_dev>yes</skip_dev>
<skip_proc>yes</skip_proc>
<skip_sys>yes</skip_sys>
<process_priority>10</process_priority>
<max_eps>50</max_eps>
<synchronization>
<enabled>yes</enabled>
<interval>5m</interval>
<max_eps>10</max_eps>
</synchronization>
</syscheck>
<!-- Log analysis -->
<localfile>
<log_format>command</log_format>
<command>df -P</command>
<frequency>360</frequency>
</localfile>
<localfile>
<log_format>full_command</log_format>
<command>netstat -tulpn | sed 's/\([[:alnum:]]\+\)\ \+[[:digit:]]\+\ \+[[:digit:]]\+\ \+\(.*\):\([[:digit:]]*\)\ \+\([0-9\.\:\*]\+\).\+\ \([[:digit:]]*\/[[:alnum:]\-]*\).*/\1 \2 == \3 == \4 \5/' | sort -k 4 -g | sed 's/ == \(.*\) ==/:\1/' | sed 1,2d</command>
<alias>netstat listening ports</alias>
<frequency>360</frequency>
</localfile>
<localfile>
<log_format>full_command</log_format>
<command>last -n 20</command>
<frequency>360</frequency>
</localfile>
<!-- Active response -->
<active-response>
<disabled>no</disabled>
<ca_store>etc/wpk_root.pem</ca_store>
<ca_verification>yes</ca_verification>
</active-response>
<logging>
<log_format>plain</log_format>
</logging>
<!-- MONITOREO DE LOGS DE CONTENEDORES (AGREGADO MANUALMENTE) -->
<localfile>
<log_format>journald</log_format>
<location>journald</location>
</localfile>
<localfile>
<log_format>apache</log_format>
<location>/var/log/nginx/access.log</location>
</localfile>
<localfile>
<log_format>apache</log_format>
<location>/var/log/nginx/error.log</location>
</localfile>
<localfile>
<log_format>apache</log_format>
<location>/var/log/apache2/access.log</location>
</localfile>
<localfile>
<log_format>apache</log_format>
<location>/var/log/apache2/error.log</location>
</localfile>
<!-- LOGS DE CONTENEDORES DOCKER (PUNTO CRÍTICO) -->
<localfile>
<log_format>syslog</log_format>
<location>/home/cft/logs_victima/*.log</location>
</localfile>
<localfile>
<log_format>syslog</log_format>
<location>/home/cft/logs_victima/access.log</location>
</localfile>
<localfile>
<log_format>apache</log_format>
<location>/home/cft/logs_victima/error.log</location>
</localfile>
<localfile>
<log_format>apache</log_format>
<location>/home/cft/logs_victima/access.log</location>
</localfile>
</ossec_config>
4.2.5. Verificación de la Conexión del Agente
Una vez configurado, verificamos que el agente se conectaba correctamente al manager desde el dashboard de Wazuh.
Captura: Agente ‘CTF-Labs-nyx’ conectado y activo en el panel de Wazuh.
4.2.6. Deshabilitación del Enrollment Automático
Para evitar que el agente intente registrarse automáticamente ignorando el archivo client.keys, se modificó el archivo de opciones internas:
1
2
3
4
5
6
7
8
9
10
# /var/ossec/etc/local_internal_options.conf
#
# This file should be handled with care. It contains
# run time modifications that can affect the use
# of OSSEC. Only change it if you know what you
# are doing. Look first at ossec.conf
# for most of the things you want to change.
#
# This file will not be overwritten during upgrades.
agent.auto_enroll=0
4.2.6.1 Script de Despliegue Automatizado de Contenedores Vulnerables
Se desarrolló un script personalizado auto_deploy_wazuh.sh que automatiza el despliegue de contenedores vulnerables con integración directa de logs para Wazuh. Script completo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#!/bin/bash
# Colores ANSI
CRE='\033[31m' # Rojo
CYE='\033[33m' # Amarillo
CGR='\033[32m' # Verde
CBL='\033[34m' # Azul
CBLE='\033[36m' # Cyan
CBK='\033[37m' # Blanco
CGY='\033[38m' # Gris
BLD='\033[1m' # Negrita
CNC='\033[0m' # Resetear colores
printf "\n"
printf "\t ${CRE} ## ${CBK} . \n"
printf "\t ${CRE} ## ## ## ${CBK} == \n"
printf "\t ${CRE}## ## ## ## ${CBK}=== \n"
printf "\t /\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\\\___/ === \n"
printf "\t ${CBL}~~~ ${CBK}{${CBL}~~ ~~~~ ~~~ ~~~~ ~~ ~ ${CBK}/ ===- ${CBL}~~~${CBK}\n"
printf "\t \\\______${CBK} o ${CBK} __/ \n"
printf "\t \\\ \\\ __/ \n"
printf "\t \\\____\\\______/ \n"
printf "${BLD}${CBLE} \n"
printf " ___ ____ ____ _ _ ____ ____ _ ____ ___ ____ \n"
printf " | \ | | | |_/ |___ |__/ | |__| |__] [__ \n"
printf " |__/ |__| |___ | \_ |___ | \ |___ | | |__] ___] \n"
printf "${CNC} \n"
printf "\t\t\t\t ${CRE} ${CNC}${CYE} ${text}${CNC} ${CRE}${CNC}\n"
# Banner hecho por Ch4rum - https://instagram.com/ch4rum
# Recorre cada uno de los nombres proporcionados como parámetros
for name in "$@"; do
base_name=$(basename "$name" .tar)
image_id=$(docker images -q "$base_name")
if [ ! -z "$image_id" ]; then
echo -e "\e[38;5;230;1mSe han detectado máquinas de DockerLabs previas, debemos limpiarlas para evitar problemas, espere un momento...\e[0m"
container_ids=$(docker ps -a -q --filter "ancestor=$image_id")
if [ ! -z "$container_ids" ]; then
docker stop $container_ids > /dev/null 2>&1
docker rm $container_ids > /dev/null 2>&1
fi
fi
container_ids=$(docker ps -aq --filter "id=5938*")
if [ ! -z "$container_ids" ]; then
echo -e "\e[38;5;230;1mSe han detectado máquinas de DockerLabs previas, debemos limpiarlas para evitar problemas, espere un momento...\e[0m"
docker stop $container_ids > /dev/null 2>&1
docker rm $container_ids > /dev/null 2>&1
fi
done
for name in "$@"; do
base_name=$(basename "$name" .tar)
image_id=$(docker images -q "$base_name")
if [ ! -z "$image_id" ]; then
echo -e "\e[38;5;230;1mSe han detectado máquinas de DockerLabs previas, debemos limpiarlas para evitar problemas, espere un momento...\e[0m"
docker rmi -f "$image_id" > /dev/null 2>&1
fi
done
detener_y_eliminar_contenedor() {
IMAGE_NAME="${TAR_FILE%.tar}"
CONTAINER_NAME="${IMAGE_NAME}_container"
if [ "$(docker ps -a -q -f name=$CONTAINER_NAME -f status=exited)" ]; then
docker rm $CONTAINER_NAME > /dev/null
fi
if [ "$(docker ps -q -f name=$CONTAINER_NAME)" ]; then
docker stop $CONTAINER_NAME > /dev/null
docker rm $CONTAINER_NAME > /dev/null
fi
if [ "$(docker images -q $IMAGE_NAME)" ]; then
docker rmi $IMAGE_NAME > /dev/null
fi
if docker network inspect $NETWORK_NAME > /dev/null 2>&1; then
docker network rm $NETWORK_NAME > /dev/null
fi
}
trap ctrl_c INT
function ctrl_c() {
echo -e "\e[1mEliminando el laboratorio, espere un momento...\e[0m"
detener_y_eliminar_contenedor
echo -e "\nEl laboratorio ha sido eliminado por completo del sistema."
exit 0
}
if [ $# -ne 1 ]; then
echo "Uso: $0 <archivo_tar>"
exit 1
fi
if ! command -v docker &> /dev/null; then
echo -e "\033[1;36m\nDocker no está instalado. Instalando Docker...\033[0m"
sudo apt update
sudo apt install docker.io -y
echo -e "\033[1;36m\nEstamos habilitando el servicio de docker. Espere un momento...\033[0m"
sleep 10
systemctl restart docker && systemctl enable docker
if [ $? -eq 0 ]; then
echo "Docker ha sido instalado correctamente."
else
echo "Error al instalar Docker. Por favor, verifique y vuelva a intentarlo."
exit 1
fi
fi
TAR_FILE="$1"
echo -e "\e[1;93m\nEstamos desplegando la máquina vulnerable, espere un momento.\e[0m"
detener_y_eliminar_contenedor
docker load -i "$TAR_FILE" > /dev/null
if [ $? -eq 0 ]; then
IMAGE_NAME=$(basename "$TAR_FILE" .tar)
CONTAINER_NAME="${IMAGE_NAME}_container"
NETWORK_NAME="dockernetwork"
if docker network inspect $NETWORK_NAME > /dev/null 2>&1; then
echo -e "\e[38;5;230;1mLa red $NETWORK_NAME ya existe. Eliminándola y recreándola...\e[0m"
docker network rm $NETWORK_NAME > /dev/null
fi
docker network create $NETWORK_NAME > /dev/null
# 🔥 PUNTO CRÍTICO: Ruta donde se guardan los logs para Wazuh
LOGS_HOST="/home/cft/logs_victima"
# Crear carpeta con permisos para Wazuh
mkdir -p $LOGS_HOST
chmod 755 $LOGS_HOST
sudo chown root:wazuh $LOGS_HOST
# Ejecutar contenedor con volumen montado en /var/log/apache2
docker run -d --name $CONTAINER_NAME \
--network $NETWORK_NAME \
-v $LOGS_HOST:/var/log/apache2 \
$IMAGE_NAME > /dev/null
IP_ADDRESS=$(docker inspect -f '' $CONTAINER_NAME)
echo -e "\e[1;96m\nMáquina desplegada, su dirección IP es --> \e[0m\e[1;97m$IP_ADDRESS\e[0m"
echo -e "\e[1;96mLos logs de Apache están en: \e[0m\e[1;97m$LOGS_HOST\e[0m"
echo -e "\e[1;91m\nPresiona Ctrl+C cuando termines con la máquina para eliminarla\e[0m"
else
echo -e "\e[91m\nHa ocurrido un error al cargar el laboratorio en Docker.\e[0m"
exit 1
fi
# Mantiene el script vivo
while true; do
sleep 1
done
Características clave del script:
- Monta automáticamente los logs en
/home/cft/logs_victima/(carpeta monitoreada por Wazuh) - Establece permisos
root:wazuhpara que el agente pueda leer los logs - Crea una red aislada para los contenedores
- Muestra la IP del contenedor desplegado
4.2.7. Configuración Definitiva del Agente para Monitoreo de Logs
La configuración final del agente en /var/ossec/etc/ossec.conf incluye múltiples entradas para garantizar la captura de todos los logs:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- Monitoreo de logs de Apache desde contenedores -->
<localfile>
<log_format>apache</log_format>
<location>/home/cft/logs_victima/access.log</location>
</localfile>
<localfile>
<log_format>apache</log_format>
<location>/home/cft/logs_victima/error.log</location>
</localfile>
<!-- Monitoreo genérico de todos los logs en la carpeta -->
<localfile>
<log_format>syslog</log_format>
<location>/home/cft/logs_victima/*.log</location>
</localfile>
Nota: Se observaron advertencias de archivos duplicados (WARNING: (1958): Log file is duplicated), pero no afectan el funcionamiento del sistema.
5. Simulación de Ataques y Validación
Para probar la detección del SIEM, se realizaron varios ataques controlados desde la máquina atacante Nyx (192.168.0.27) hacia el contenedor WordPress en CTF-Labs.
5.1. Ataque de Fuerza Bruta con Hydra
Hydra es una herramienta de inicio de sesión en red que puede realizar ataques de fuerza bruta rápida.
5.1.1. Preparación del Diccionario
En la máquina Nyx, creamos un pequeño diccionario de contraseñas para la prueba.
1
2
3
4
5
6
7
8
cupp -i (entrar en modo interactivo en la herramienta)
(a continuación lo que se rellena, lo que no aparezca se completa como vacío o negativo (No))
> First Name: Luis
> Surname:
> Nickname: TLuisillo_o
> Birthdate (DDMMYYYY): 09101981
> Do you want to add some key words about the victim? Y/[N]: y
> Please enter the words, separated by comma. [i.e. hacker,juice,black], spaces will be removed: 19131337
5.1.2. Ejecución del Ataque con Hydra
Lanzamos un ataque contra el formulario de login de WordPress (wp-login.php).
1
2
3
hydra -l luisillo -P passwords.txt 192.168.0.21 -s 8080 http-post-form \
"/wordpress/wp-login.php:log=^USER^&pwd=^PASS^&wp-submit=Entrar&testcookie=1:S=Location" \
-t 64 -f -V
Explicación del comando:
-l luisillo: Usuario a probar.-P passwords.txt: Archivo de contraseñas.-s 8080: Puerto del contenedor.http-post-form: Módulo para atacar formularios web.-t 64: 64 hilos en paralelo (muy rápido).-f: Se detiene al encontrar la primera contraseña válida.-V: Modo verbose para ver cada intento.
5.2. Ataque de Reconocimiento con WPScan
WPScan es un escáner de vulnerabilidades para WordPress.
5.2.1. Ejecución de WPScan
Lanzamos un escaneo básico para identificar plugins, temas y usuarios.
wpscan --url http://192.168.0.21:8080/wordpress --enumerate u
5.2.2. Ataque de Fuerza Bruta con WPScan (más lento)
1
2
3
4
5
wpscan --url http://192.168.0.21:8080/wordpress \
--usernames luisillo \
--passwords passwords.txt \
--password-attack wp-login \
--max-threads 50
Nota: Se observó que WPScan es significativamente más lento que Hydra para fuerza bruta, ya que realiza muchas peticiones de reconocimiento antes de empezar los intentos de login.
5.3. Monitorización en Tiempo Real Durante los Ataques
Mientras se ejecutaban los ataques, se monitorizaron los logs en la máquina objetivo para verificar que el tráfico llegaba.
En CTF-Labs (host):
1
sudo tail -f /var/log/apache2/access.log | grep --color=auto -E "POST|wp-login|luisillo|Hydra"
5.4. Verificación del Estado del Agente en el Manager
Una vez conectado, se verificó el estado del agente desde el manager:
1
2
# En Raspberry Pi (dentro del contenedor manager)
/var/ossec/bin/agent_control -l
Resultado:
1
ID: 006, Name: cft, IP: any, Active
El agente con ID 006 (nombre “cft”) se muestra como Active, confirmando la correcta comunicación bidireccional.
📋 Apéndice D: Credenciales del Laboratorio
| Servicio | Usuario | Contraseña | Notas | | ———————- | ———– | —————————————————————— | —————————————– | | Wazuh Dashboard | admin | SecretPassword | Acceso web a https://192.168.0.200:8443 | | Wazuh Indexer | admin | SecretPassword | Backend de datos | | Wazuh API | wazuh-wui | MyS3cr37P450r.*- | Para comunicación dashboard-manager | | Agente Wazuh (ID 006) | cft | cd53614b2e09e64524b40c6f41fee0d09edb9b04aaf202f483a46df3d3605bc7 | Clave en /var/ossec/etc/client.keys | | WordPress (contenedor) | luisillo | (la que se encuentre en el diccionario) | Usuario de prueba para ataques |
6. Resultados y Análisis en Wazuh
Tras los ataques, se procedió a analizar las alertas generadas en el dashboard de Wazuh.
| ID/Referencia | Descripción | | —————————————————————— | —————————————————————————– | | agent.auto_enroll=0 | Opción en local_internal_options.conf para deshabilitar registro automático | | cd53614b2e09e64524b40c6f41fee0d09edb9b04aaf202f483a46df3d3605bc7 | Clave del agente 006 | | SecretPassword | Contraseña del dashboard y indexer | | MyS3cr37P450r.*- | Contraseña de la API de Wazuh | | /home/cft/logs_victima | Punto de montaje de logs de contenedores |
6.1. Detección de Intentos de Login (Regla 31509)
Cada intento de login, tanto de Hydra como de WPScan, generó una alerta de nivel 3.
Captura: Múltiples alertas de tipo “CMS (WordPress or Joomla) login attempt.” (regla 31509) desde la IP 172.17.0.1.
Detalle de una alerta de Hydra:
- Regla: 31509
- Nivel: 3
- Descripción: CMS (WordPress or Joomla) login attempt.
- User-Agent:
Mozilla/5.0 (Hydra) - Métrica:
rule.firedtimesllegó a 902 para esta regla durante el ataque.
Detalle de una alerta de WPScan:
- Regla: 31509
- Nivel: 3
- User-Agent:
WPScan v3.8.28
6.2. Correlación y Detección de Fuerza Bruta (Regla 31510)
Al acumularse múltiples intentos fallidos en un corto periodo de tiempo, Wazuh correlacionó los eventos y elevó la alerta a nivel 8 (fuerza bruta).
Captura: Alerta de “CMS (WordPress or Joomla) brute force attempt.” (regla 31510) con nivel 8.
Detalle de la alerta de fuerza bruta:
- Regla: 31510
- Nivel: 8
- Descripción: CMS (WordPress or Joomla) brute force attempt.
- Frecuencia:
rule.frequency: 8(detecta 8 o más intentos en el periodo definido). - Campo
previous_output: Muestra los múltiples intentos de login que causaron la alerta, evidenciando la ráfaga de ataques en el mismo segundo.
previous_output: 172.17.0.1 - - [09/Mar/2026:07:26:04 -0900] "POST /wordpress/wp-login.php HTTP/1.0" 200 7426 "-" "Mozilla/5.0 (Hydra)" [x8]
6.3. Análisis MITRE ATT&CK
Las alertas se enriquecieron automáticamente con información de MITRE ATT&CK.
- Táctica: Credential Access
- Técnica: Brute Force (T1110) y Password Guessing (T1110.001)
6.4. Resumen de Hallazgos en el Dashboard
| Estadística | Valor |
|---|---|
| Total de Alertas Generadas | 1,822 hits |
| Alertas de Nivel 8 (Fuerza Bruta) | Múltiples (regla 31510) |
| Alertas de Nivel 3 (Intentos de Login) | >900 (regla 31509) |
| IP del Atacante (vista desde el contenedor) | 172.17.0.1 |
| Herramientas Detectadas | Hydra, WPScan |
7. Lecciones Aprendidas y Desafíos Superados
NAT de Docker y Visibilidad de IPs: El principal desafío fue que Docker hace NAT, por lo que dentro del contenedor, la IP del atacante se veía como
172.17.0.1en lugar de la IP real192.168.0.27. La solución fue monitorizar desde el host, aunque para ver la IP real en las alertas de Wazuh se requeriría una herramienta como Suricata en el host.Permisos en Disco Externo: El mayor quebradero de cabeza fueron los permisos del disco de 2TB. El comando
chownno funcionaba como se esperaba, manteniendo los archivos con propietarionyx-pi. Esto impedía que los contenedores de Wazuh (que corren como usuario 1000) escribieran en el disco. La solución de emergencia fue forzar al contenedor a correr comoroot(user: "0:0") y dar permisos totales al disco (chmod -R 777).Arquitectura ARM64: La Raspberry Pi 5 utiliza una arquitectura ARM64, mientras que muchas imágenes Docker están precompiladas para AMD64. Fue crucial identificar que las versiones de Wazuh 4.7.2 son las primeras con soporte multi-arquitectura, eliminando la necesidad de especificar la plataforma manualmente.
Redirecciones Web de WordPress: WordPress estaba configurado para redirigir el tráfico del puerto 8080 al 80, lo que inicialmente causaba problemas con WPScan. La solución fue atacar directamente a
wp-login.phpy usar la opción--follow-redirection falseen WPScan cuando era necesario.Rendimiento de las Herramientas: Se confirmó que Hydra es mucho más eficaz para generar un gran volumen de eventos de fuerza bruta en poco tiempo, mientras que WPScan es más lento debido a sus fases de reconocimiento. Esto es útil para elegir la herramienta según el objetivo de la prueba.
Formato del archivo client.keys: Un error crítico fue que el archivo
client.keysno tenía el formato correcto, causando errores de “Invalid password”. La solución fue usarecho "ID:name:any:KEY" | sudo tee /var/ossec/etc/client.keyspara garantizar el formato adecuado con salto de línea al final.Enrollment automático del agente: El agente intentaba registrarse automáticamente ignorando el archivo
client.keys. Se solucionó añadiendoagent.auto_enroll=0en/var/ossec/etc/local_internal_options.conf.Múltiples secciones
<ossec_config>: Inicialmente, la configuración del agente tenía dos secciones<ossec_config>, lo que impedía que se aplicaran correctamente las reglas de monitoreo de logs. La solución fue unificar todo en una sola sección.
| Formato incorrecto de client.keys | Error “Invalid password” en logs del agente | Usar echo "ID:name:any:KEY" \| sudo tee /var/ossec/etc/client.keys para garantizar el formato correcto |
| Enrollment automático activado | Agente ignora client.keys e intenta registrarse | Añadir agent.auto_enroll=0 en local_internal_options.conf |
| Puerto 443 ocupado | Error al levantar dashboard | Cambiar a puerto 8443 en docker-compose.yml |
| Permisos en disco externo | chown no funciona en disco de 2TB | Usar sudo chmod -R 777 y user: "0:0" en el contenedor |
8. Comandos de Verificación y Troubleshooting
Cómo usar el script:
1
2
3
4
5
6
7
8
9
10
11
# Dar permisos de ejecución
chmod +x auto_deploy_wazuh.sh
# Ejecutar con un archivo .tar de DockerLabs
./auto_deploy_wazuh.sh escolares.tar
# La salida mostrará:
# Máquina desplegada, su dirección IP es --> 172.17.0.2
# Los logs de Apache están en: /home/cft/logs_victima
# Para detener y eliminar el contenedor: Ctrl+C
Para verificar el estado del cluster Docker en la Raspberry Pi:
1
2
docker ps | grep wazuh
docker logs -f wazuh-demo1-wazuh.manager-1
Para verificar la conexión del agente en CTF-Labs:
1
2
sudo /var/ossec/bin/agent_control -l
sudo tail -f /var/ossec/logs/ossec.log | grep -i "connected"
Para forzar la inicialización de seguridad del Indexer (si hay error 503):
1
docker exec -it wazuh-demo1-wazuh.indexer-1 /usr/share/wazuh-indexer/plugins/opensearch-security/tools/securityadmin.sh -cd /usr/share/wazuh-indexer/plugins/opensearch-security/securityconfig/ -icl -nhnv -cacert /usr/share/wazuh-indexer/config/certs/root-ca.pem -cert /usr/share/wazuh-indexer/config/certs/admin.pem -key /usr/share/wazuh-indexer/config/certs/admin-key.pem -h wazuh.indexer
Para verificar que el agente está monitoreando los logs de contenedores:
1
sudo tail -f /var/ossec/logs/ossec.log | grep -i "victima\|analyzing"
Para verificar el ID correcto del agente:
1
2
sudo cat /var/ossec/etc/client.keys
/var/ossec/bin/agent_control -l # En el manager
8.1. Problemas Específicos Encontrados y Soluciones
| Problema | Síntoma | Solución |
|---|---|---|
| Error de permisos en disco externo | chown no funcionaba en disco de 2TB | sudo chmod -R 777 /mnt/datos/wazuh_data y user: "0:0" en docker-compose |
| Archivo client.keys con formato incorrecto | Error “Invalid password” en logs del agente | Usar echo "ID:name:any:KEY" \| sudo tee /var/ossec/etc/client.keys |
| Enrollment automático activado | Agente ignora client.keys e intenta registrarse | Añadir agent.auto_enroll=0 en local_internal_options.conf |
Múltiples secciones <ossec_config> | Configuración de logs no se aplicaba | Unificar todo en una sola sección |
| Rutas duplicadas en ossec.conf | WARNING: (1958) en logs | Eliminar entradas redundantes (opcional) |
| Contenedor no genera logs | No hay archivos en /home/cft/logs_victima/ | Verificar montaje: -v $LOGS_HOST:/var/log/apache2 |
| IP del atacante enmascarada | Alertas muestran 172.17.0.1 en lugar de IP real | Usar Suricata en host o configurar Docker para preservar IP |
9. Conclusión y Próximos Pasos
La implementación de este laboratorio SIEM casero con Wazuh sobre una Raspberry Pi 5 ha sido un éxito rotundo. Más allá de tener un sistema funcional, el verdadero valor del proyecto ha sido el viaje: enfrentarse a problemas reales de compatibilidad ARM64, lidiar con permisos en sistemas de archivos, comprender las complejidades de la red en Docker y, finalmente, ver cómo un ataque simulado de fuerza bruta se traduce en alertas enriquecidas con MITRE ATT&CK en un dashboard profesional.
Este entorno no es solo un “juguete”, sino una plataforma de entrenamiento sólida que replica los flujos de trabajo de un Centro de Operaciones de Ciberseguridad (SOC) real.
Próximos pasos recomendados:
Implementar reglas personalizadas para WordPress en Wazuh
Configurar alertas por correo electrónico para notificaciones en tiempo real
Añadir más agentes (Windows, Linux) para diversificar el laboratorio
Integrar Suricata para detección de intrusiones a nivel de red
Automatizar el despliegue con scripts de Ansible
📋 Apéndice A: Glosario de IDs y Referencias
| ID/Referencia | Descripción |
|---|---|
| 006 | ID del agente Wazuh en la máquina CTF-Labs (Kali) |
| 005 | ID anterior del agente (reemplazado por 006) |
| wazuh-demo1-wazuh.manager-1 | Nombre del contenedor del manager |
| 172.17.0.2 | IP del contenedor vulnerable dentro de Docker |
| 192.168.0.200 | IP de la Raspberry Pi (manager) |
| 192.168.0.21 | IP de CTF-Labs (agente) |
| /home/cft/logs_victima | Punto de montaje de logs en el host |
| agent.auto_enroll=0 | Opción para deshabilitar el registro automático |
| 31509 | Regla de Wazuh: “CMS login attempt” |
| 31510 | Regla de Wazuh: “CMS brute force attempt” |
🏆 Logros Clave del Proyecto
✅ Arquitectura funcional: SIEM completo con Raspberry Pi 5 como servidor central
✅ Recolección de logs de contenedores: Logs de Apache desde contenedores Docker monitorizados por el agente host
✅ Detección de ataques: Reglas 31509 y 31510 detectaron exitosamente intentos de login y fuerza bruta
✅ Integración MITRE ATT&CK: Alertas enriquecidas con T1110 (Brute Force)
✅ Persistencia en disco externo: Datos almacenados en disco de 2TB, no en la SD
✅ Resolución de problemas críticos: Permisos, ARM64, formato de claves, enrollment automático
✅ Documentación completa: Proceso replicable y bien documentado para futuras referencias


