Wazuh analiza los logs que envían sus agentes en dos fases: los decodificadores extraen campos (usuario, IP de origen, acción) y las reglas deciden si ese evento genera una alerta y con qué nivel. Las reglas incluidas cubren SSH, sudo, servidores web y mucho más, pero tus aplicaciones internas necesitan las suyas. En este tutorial crearás un decodificador y varias reglas para una aplicación propia, las probarás con wazuh-logtest, correlacionarás intentos repetidos, bloquearás la IP atacante con respuesta activa y aprenderás a silenciar falsos positivos sin tocar las reglas originales.
Requisitos previos
- Un servidor Wazuh 4.x (manager, indexer y dashboard) funcionando. Los comandos usan las rutas estándar de
/var/ossec. - Al menos un agente Linux (por ejemplo Ubuntu 24.04 en un VPS de CubePath) registrado y conectado al manager.
- Acceso
sudoen el manager y en el agente. - Nociones básicas de XML.
Comprueba la versión del manager y que el agente está activo:
sudo /var/ossec/bin/wazuh-control info
sudo /var/ossec/bin/agent_control -l
WAZUH_VERSION="v4.x.x"
WAZUH_REVISION="..."
WAZUH_TYPE="server"
Wazuh agent_control. List of available agents:
ID: 000, Name: wazuh-manager (server), IP: 127.0.0.1, Active/Local
ID: 001, Name: web01, IP: any, Active
Dónde van tus reglas y decodificadores
Nunca edites los ficheros de /var/ossec/ruleset/: las actualizaciones de Wazuh los sobrescriben. Lo tuyo va en:
| Fichero | Contenido |
|---|---|
/var/ossec/etc/decoders/local_decoder.xml | Decodificadores propios |
/var/ossec/etc/rules/local_rules.xml | Reglas propias |
/var/ossec/etc/lists/ | Listas CDB (IPs, usuarios) |
Usa identificadores de regla entre 100000 y 120000, el rango reservado para reglas de usuario.
El ejemplo de esta guía es una aplicación llamada miapp que escribe líneas como esta en /var/log/miapp/app.log:
Sep 25 10:30:45 web01 miapp[2154]: login failed user=admin src=203.0.113.10
Paso 1: Crear el decodificador
El predecodificador de Wazuh ya separa la fecha, el host y el nombre del programa (miapp) de las líneas con formato syslog. Tu decodificador solo tiene que reconocer el programa y extraer el usuario y la IP. En el manager, abre el fichero de decodificadores locales:
sudo nano /var/ossec/etc/decoders/local_decoder.xml
Añade al final:
<decoder name="miapp">
<program_name>^miapp$</program_name>
</decoder>
<decoder name="miapp-login">
<parent>miapp</parent>
<regex>^login (\w+) user=(\S+) src=(\S+)$</regex>
<order>action, srcuser, srcip</order>
</decoder>
El decodificador padre selecciona los eventos de miapp; el hijo extrae tres campos. srcip y srcuser son nombres estándar de Wazuh, lo que permite usarlos luego en correlación y en respuesta activa. Las expresiones de <regex> usan la sintaxis propia de Wazuh (OS_Regex), que admite \w, \S, \d, + y *, pero no cuantificadores como {4}.
Paso 2: Crear las reglas
Abre el fichero de reglas locales:
sudo nano /var/ossec/etc/rules/local_rules.xml
Añade un grupo nuevo con tres reglas encadenadas:
<group name="miapp,">
<!-- Regla base: cualquier evento de miapp, sin alerta (nivel 0) -->
<rule id="100100" level="0">
<decoded_as>miapp</decoded_as>
<description>miapp: evento</description>
</rule>
<!-- Login fallido -->
<rule id="100101" level="5">
<if_sid>100100</if_sid>
<field name="action">^failed$</field>
<description>miapp: login fallido de $(srcuser) desde $(srcip)</description>
<group>authentication_failed,</group>
</rule>
<!-- 6 fallos desde la misma IP en 2 minutos -->
<rule id="100102" level="10" frequency="6" timeframe="120">
<if_matched_sid>100101</if_matched_sid>
<same_srcip />
<description>miapp: posible fuerza bruta desde $(srcip)</description>
<mitre>
<id>T1110</id>
</mitre>
<group>authentication_failures,</group>
</rule>
</group>
decoded_asenlaza con el nombre del decodificador padre.if_sidhace que la regla 100101 solo se evalúe si antes coincidió la 100100.frequency,timeframe,if_matched_sidysame_srcipconvierten eventos sueltos en una alerta de correlación.$(srcip)inserta el valor del campo en la descripción de la alerta.
Comprueba que la configuración carga sin errores:
sudo /var/ossec/bin/wazuh-analysisd -t
Si termina sin mensajes de ERROR ni CRITICAL, la configuración es correcta. Un error de XML indicará el fichero y la línea.
Paso 3: Probar con wazuh-logtest
wazuh-logtest pasa una línea de log por el mismo motor que usa el manager y muestra cada fase, sin necesidad de generar eventos reales:
sudo /var/ossec/bin/wazuh-logtest
Pega la línea de ejemplo y pulsa Intro:
Sep 25 10:30:45 web01 miapp[2154]: login failed user=admin src=203.0.113.10
**Phase 1: Completed pre-decoding.
full event: 'Sep 25 10:30:45 web01 miapp[2154]: login failed user=admin src=203.0.113.10'
timestamp: 'Sep 25 10:30:45'
hostname: 'web01'
program_name: 'miapp'
**Phase 2: Completed decoding.
name: 'miapp'
action: 'failed'
srcip: '203.0.113.10'
srcuser: 'admin'
**Phase 3: Completed filtering (rules).
id: '100101'
level: '5'
description: 'miapp: login fallido de admin desde 203.0.113.10'
groups: '['miapp', 'authentication_failed']'
firedtimes: '1'
mail: 'False'
**Alert to be generated.
Pega la misma línea seis veces seguidas: la última debe disparar la regla 100102 con nivel 10. Sal con Ctrl+C.
Si la fase 2 no muestra tus campos, el problema está en el decodificador; si la fase 3 muestra otra regla, revisa if_sid y field.
Cuando todo cuadre, reinicia el manager para cargar las reglas:
sudo systemctl restart wazuh-manager
Paso 4: Recoger el log en el agente
En el agente, indica a Wazuh qué fichero debe leer. Edita su configuración:
sudo nano /var/ossec/etc/ossec.conf
Añade este bloque dentro de <ossec_config>:
<localfile>
<log_format>syslog</log_format>
<location>/var/log/miapp/app.log</location>
</localfile>
sudo systemctl restart wazuh-agent
Genera un evento de prueba en el agente:
sudo mkdir -p /var/log/miapp
echo "$(date '+%b %e %H:%M:%S') $(hostname) miapp[2154]: login failed user=admin src=203.0.113.10" | sudo tee -a /var/log/miapp/app.log
En el manager, busca la alerta:
sudo grep '"id":"100101"' /var/ossec/logs/alerts/alerts.json | tail -n 1
La alerta también aparece en el dashboard, en Threat Hunting, filtrando por rule.id: 100101.
Paso 5: Bloquear la IP con respuesta activa
La respuesta activa ejecuta un script en el agente cuando salta una regla. Wazuh incluye firewall-drop, que bloquea la IP de origen con iptables durante el tiempo indicado. El comando ya está definido en el ossec.conf del manager; solo falta asociarlo a tu regla.
En el manager:
sudo nano /var/ossec/etc/ossec.conf
Añade dentro de <ossec_config>:
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100102</rules_id>
<timeout>600</timeout>
</active-response>
location en local ejecuta la acción en el agente que generó el evento y timeout deshace el bloqueo a los 10 minutos. La regla necesita un campo srcip, por eso era importante extraerlo en el decodificador.
Protege las IPs de administración para que nunca se bloqueen. En el mismo fichero, dentro del bloque <global> que ya existe, añade una línea por IP:
<white_list>198.51.100.20</white_list>
sudo /var/ossec/bin/wazuh-analysisd -t && sudo systemctl restart wazuh-manager
Prueba el bloqueo escribiendo seis fallos seguidos en el agente (usa una IP de documentación, no la tuya):
for i in 1 2 3 4 5 6; do
echo "$(date '+%b %e %H:%M:%S') $(hostname) miapp[2154]: login failed user=admin src=203.0.113.10" | sudo tee -a /var/log/miapp/app.log > /dev/null
done
En el agente, comprueba el registro de respuesta activa y la regla del firewall:
sudo tail -n 3 /var/ossec/logs/active-responses.log
sudo iptables -S INPUT | grep 203.0.113.10
-A INPUT -s 203.0.113.10/32 -j DROP
Paso 6: Reducir falsos positivos con listas CDB
Una regla hija de nivel 0 anula la alerta de su regla padre cuando se cumple una condición. Combinada con una lista CDB, permite silenciar, por ejemplo, los fallos de SSH que vienen del servidor de monitorización sin modificar el ruleset.
En el manager, crea la lista con una IP por línea en formato clave:valor:
sudo nano /var/ossec/etc/lists/ips-confianza
198.51.100.20:monitorizacion
198.51.100.21:bastion
Asigna la propiedad correcta y declara la lista en el bloque <ruleset> de /var/ossec/etc/ossec.conf:
sudo chown wazuh:wazuh /var/ossec/etc/lists/ips-confianza
sudo nano /var/ossec/etc/ossec.conf
<list>etc/lists/ips-confianza</list>
Añade la regla que silencia los intentos de usuario inexistente por SSH (regla 5710) desde esas IPs en local_rules.xml, dentro de un grupo:
<group name="local,sshd,">
<rule id="100200" level="0">
<if_sid>5710</if_sid>
<list field="srcip" lookup="address_match_key">etc/lists/ips-confianza</list>
<description>sshd: usuario inexistente desde IP de confianza (silenciado)</description>
</rule>
</group>
El manager compila las listas al reiniciarse:
sudo /var/ossec/bin/wazuh-analysisd -t && sudo systemctl restart wazuh-manager
Verifícalo en wazuh-logtest con una línea de sshd:
Sep 25 11:02:10 web01 sshd[3321]: Invalid user test from 198.51.100.20 port 52144
La fase 3 debe mostrar id: '100200' y level: '0', sin Alert to be generated. Con otra IP de origen seguirá saltando la regla 5710.
Paso 7: Vigilar ficheros críticos en tiempo real
El módulo de integridad de ficheros (FIM, syscheck) compara hashes y permisos. Por defecto escanea cada 12 horas; con realtime avisa en segundos. En el agente, dentro del bloque <syscheck> de /var/ossec/etc/ossec.conf, añade:
<directories realtime="yes" check_all="yes">/etc/ssh</directories>
<directories realtime="yes" check_all="yes" report_changes="yes">/var/www/html</directories>
report_changes guarda un diff del contenido en la alerta; úsalo solo en ficheros de texto pequeños, nunca en directorios con secretos. Reinicia y provoca un cambio:
sudo systemctl restart wazuh-agent
echo "<!-- prueba FIM -->" | sudo tee -a /var/www/html/index.html
En el manager, las modificaciones generan la regla 550 (checksum cambiado) y los ficheros nuevos la 554:
sudo grep '"id":"550"' /var/ossec/logs/alerts/alerts.json | tail -n 1
Solución de problemas
wazuh-analysisd -t falla con un error de XML. Suele ser un < o & sin escapar dentro de <match> o <regex>. Escríbelos como < y &.
La regla funciona en wazuh-logtest pero no llega ninguna alerta. Revisa que el agente lee el fichero (sudo grep miapp /var/ossec/logs/ossec.log en el agente debe mostrar Analyzing file: '/var/log/miapp/app.log') y que las líneas tienen el mismo formato que la de prueba.
La respuesta activa no bloquea. Comprueba que la alerta contiene srcip, que la IP no está en <white_list> y revisa /var/ossec/logs/active-responses.log en el agente.
Demasiadas alertas de FIM. Excluye rutas ruidosas con <ignore>/ruta</ignore> dentro de <syscheck> o crea una regla hija de nivel 0 sobre la 550 para esa ruta.
Conclusión
Has creado un decodificador y reglas propias, las has validado con wazuh-logtest, has convertido fallos sueltos en una alerta de fuerza bruta con bloqueo automático y has silenciado ruido con una lista CDB. El siguiente paso lógico es escribir reglas para el resto de tus aplicaciones, revisar cada semana las reglas que más alertas generan para ajustarlas y enviar las alertas de nivel alto a tu canal de incidentes con las integraciones de Wazuh.
