
CVE-2026-39154, XSS armazenado no CometChat JS SDK
Este diretório contém uma reprodução honesta e autocontida do render sink
por trás do CVE-2026-39154: uma falha de cross-site scripting armazenado no CometChat
JavaScript SDK 3.0.12, em que o campo data.text de uma mensagem de grupo é renderizado nos
clientes de outros membros sem codificação de saída contextual.
Ele é executado inteiramente localmente com dados sintéticos. Ele não contata, sonda nem se autentica no serviço CometChat ativo ou em qualquer terceiro. É uma demonstração local apenas do caminho de renderização vulnerável do cliente.
Repositório PoC correspondente (nomeado no writeup): https://github.com/defineid/Wildfire
Writeup: writeup-cometchat-xss.html neste portfólio.
| Campo | Valor |
|---|---|
| CVE | CVE-2026-39154 |
| Fornecedor / Produto | CometChat · JavaScript SDK 3.0.12 |
| Classe | Cross-Site Scripting Armazenado (CWE-79) |
| Endpoint | POST /v3.0/messages · campo de injeção data.text |
| Receptor | receiverType: group |
| Interação | Nenhuma; o payload armazenado é executado automaticamente quando um membro abre o grupo |
| Divulgação | Reportado à CometChat em fev 2026 (solicitação 42660); fornecedor marcou como Solved; CVE atribuído pela MITRE |
| Referência | https://www.cve.org/CVERecord?id=CVE-2026-39154 |
Nota sobre a versão corrigida: o writeup publicado afirma que o fornecedor resolveu o problema ("VENDOR: SOLVED"), mas não nomeia um número específico de SDK/build corrigido, e nenhuma URL de advisory separada do fornecedor é fornecida além do registro CVE acima. Nenhum número de versão corrigida é afirmado aqui porque o writeup não fornece um.
Uma TextMessage do CometChat carrega seu texto em data.text, exposto ao código de UI como
message.getText(). Um membro autenticado pode enviar, para um grupo, uma mensagem cujo
data.text contém marcação HTML, por exemplo:
POST /v3.0/messages
{
"receiverType": "group",
"receiver": "<groupId>",
"category": "message",
"type": "text",
"data": { "text": "" }
}
A plataforma armazena essa string literalmente. Quando qualquer membro do grupo abre a
conversa, o caminho de renderização da lista de mensagens do cliente pega data.text e o insere
no DOM como HTML, sem codificação de saída contextual (uma escrita no estilo innerHTML
em vez de uma escrita textContent). A marcação `` é, portanto, interpretada;
seu src=x falha ao carregar, o que dispara o manipulador onerror, e o script do atacante
é executado na sessão da vítima. Como a mensagem é armazenada, isso é
persistente e não requer interação da vítima além de visualizar o grupo.
A correção divulgada sanitiza / codifica contextualmente esse campo, de modo que a mesma entrada é renderizada como texto inerte em vez de ser executada.
Um membro autenticado do grupo é o atacante; as vítimas são os demais membros desse grupo. O payload é entregue uma vez, armazenado no servidor e executado automaticamente no navegador de cada membro quando ele visualiza a conversa. Nenhum clique, concessão de permissão ou interação adicional é necessária. Impacto no relatório original: execução arbitrária de script na sessão de cada membro (roubo de sessão/token, ações em nome da vítima, divulgação de dados da conversa).
repro.html — uma página local simples (fundo branco, CSS mínimo, uma área simples de lista de mensagens)
que representa apenas o caminho de renderização vulnerável do SDK 3.0.12. Ela contém
uma cópia local fiel do sink não sanitizado:
function getText(message){ return message.data.text; } // mirrors TextMessage#getText()
...
body.innerHTML = getText(message); // *** the SDK 3.0.12 sink, reproduced ***
Ele renderiza um objeto sintético de mensagem armazenada através desse sink para que o script injetado seja executado localmente. Uma nota honesta inline na página declara que esta é uma reprodução local do sink do SDK 3.0.12, que o SDK corrigido sanitiza o campo, e que nenhum serviço CometChat ativo está envolvido.
payload.json — o objeto sintético armazenado TextMessage. Seu campo data.text
é o ponto de injeção (anotado com as chaves _injection_point / _note, já que
JSON não tem comentários). O vetor de injeção (``) é
idêntico ao relatório divulgado; a ação é alterada para um marcador inofensivo
na página.
payload.txt — a mesma injeção documentada em texto simples, incluindo a
requisição originalmente elaborada, com uma nota explícita de que ela não é enviada a lugar algum.
Este repro.html é um substituto mínimo escrito a partir do mecanismo descrito no writeup.
Ele não é um extrato do código-fonte do fornecedor; o SDK real não foi obtido
nem desmontado para esta reprodução.
O payload nesta reprodução é benigno: em vez de alert(document.domain), o
manipulador onerror define window.__xss_fired = true, escreve RESULT: XSS-FIRED na
página e define o título do documento. Nada é exfiltrado.
cd poc/CVE-2026-39154
python3 -m http.server 5599
# then open http://localhost:5599/repro.html in a browser
repro.html renderiza uma cópia incorporada da mensagem armazenada de forma síncrona (para que o sink dispare de forma determinística mesmo em captura headless) e adicionalmente busca payload.json para confirmar que o artefato em disco carrega e contém o mesmo campo.
RESULT: XSS-FIRED em vermelho — prova de que o script onerror injetado foi executado através do sink innerHTML não sanitizado.data.text seria codificado e apareceria como o texto literal `` sem execução de script.payload.json foi validado como JSON bem formado e confirmado por conter a injeção em data.text (um vetor ). O diretório foi servido com `python3 -m http.server 5599`, e `repro.html` foi carregado no **Firefox 140.11.0esr headless** via `--screenshot` (salvo aqui como `repro-screenshot.png`). A captura de tela obtida mostra o renderizado como um elemento de imagem quebrada (ou seja, data.text foi interpretado como HTML, não exibido como texto) e RESULT: XSS-FIRED escrito no DOM. Isso confirma que o sink innerHTML não sanitizado executa script injetado a partir do campo da mensagem./v3.0/messages, nenhuma conta ou grupo foi usado, e nenhuma build real do SDK foi baixada, executada ou desmontada. O sink aqui é um substituto fiel escrito à mão, baseado no mecanismo descrito no writeup, não no código distribuído pelo fornecedor. A versão exata do SDK corrigido não foi confirmada porque o writeup não a especifica.Esta é uma reprodução local e offline usando apenas dados sintéticos e autogerados, construída para documentar uma CVE já reportada e resolvida pelo fornecedor. Ela tem como alvo um substituto local do caminho de renderização vulnerável, não qualquer sistema ao vivo ou de terceiros. O payload é um marcador inofensivo na página; ele não contém exfiltração ou weaponização além de demonstrar que o campo é renderizado sem sanitização.