
CVE-2026-39154, хранимая XSS-уязвимость в CometChat JS SDK
В этом каталоге находится честное, самодостаточное воспроизведение sink-точки рендеринга,
лежащей в основе CVE-2026-39154: дефект хранимого межсайтового скриптинга в CometChat
JavaScript SDK 3.0.12, при котором поле data.text группового сообщения рендерится в
клиентах других участников без контекстного кодирования вывода.
Всё работает полностью локально на синтетических данных. Оно не обращается к живому сервису CometChat или любой третьей стороне, не зондирует и не аутентифицируется в нём. Это локальная демонстрация только уязвимого пути рендеринга на клиенте.
Соответствующий репозиторий PoC (указан в разборе): https://github.com/defineid/Wildfire
Разбор: writeup-cometchat-xss.html в этом портфолио.
| Поле | Значение |
|---|---|
| CVE | CVE-2026-39154 |
| Вендор / Продукт | CometChat · JavaScript SDK 3.0.12 |
| Класс | Хранимый межсайтовый скриптинг (CWE-79) |
| Эндпоинт | POST /v3.0/messages · поле инъекции data.text |
| Получатель | receiverType: group |
| Взаимодействие | Отсутствует; сохранённая полезная нагрузка автоматически выполняется, когда участник открывает группу |
| Раскрытие | Сообщено в CometChat в феврале 2026 (запрос 42660); вендор отметил как Solved; CVE присвоен MITRE |
| Ссылка | https://www.cve.org/CVERecord?id=CVE-2026-39154 |
Примечание об исправленной версии: опубликованный разбор утверждает, что вендор устранил проблему ("VENDOR: SOLVED"), но не называет конкретный номер исправленного SDK/сборки, и никакой отдельный URL рекомендации вендора не приводится помимо записи CVE выше. Номер исправленной версии здесь не утверждается, поскольку разбор его не предоставляет.
TextMessage в CometChat хранит свой текст в data.text, доступном коду UI как
message.getText(). Аутентифицированный участник может отправить в группу сообщение, чьё
data.text содержит HTML-разметку, например:
POST /v3.0/messages
{
"receiverType": "group",
"receiver": "<groupId>",
"category": "message",
"type": "text",
"data": { "text": "" }
}
Платформа сохраняет эту строку дословно. Когда любой участник группы открывает
беседу, путь отрисовки списка сообщений клиента берёт data.text и вставляет
его в DOM как HTML, без контекстного кодирования вывода (запись в стиле innerHTML,
а не textContent). Таким образом, разметка `` парсится;
её src=x не загружается, что вызывает обработчик onerror, и скрипт злоумышленника
выполняется в сессии жертвы. Поскольку сообщение сохраняется, это
постоянная уязвимость, не требующая никакого взаимодействия с жертвой, кроме просмотра группы.
Раскрытое исправление санитизирует / контекстно кодирует это поле, поэтому тот же ввод отображается как инертный текст вместо выполнения.
Атакующий — аутентифицированный участник группы; жертвы — остальные участники этой группы. Полезная нагрузка доставляется один раз, сохраняется на сервере и выполняется автоматически в браузере каждого участника при просмотре беседы. Не требуется ни клика, ни выдачи разрешений, ни какого-либо дополнительного взаимодействия. Влияние в исходном отчёте: выполнение произвольного скрипта в сессии каждого участника (кража сессии/токена, действия от имени жертвы, раскрытие данных беседы).
repro.html — голая локальная страница (простой белый фон, минимальный CSS, простая область
списка сообщений), которая замещает только уязвимый путь отрисовки SDK 3.0.12. Она содержит
точную локальную копию несанитизированного стока:
function getText(message){ return message.data.text; } // mirrors TextMessage#getText()
...
body.innerHTML = getText(message); // *** the SDK 3.0.12 sink, reproduced ***
Он отображает синтетический объект сохранённого сообщения через этот сток, так что внедрённый скрипт выполняется локально. Встроенное честное примечание на странице указывает, что это локальное воспроизведение стока SDK 3.0.12, что исправленный SDK санирует это поле и что реальный сервис CometChat не задействован.
payload.json — синтетический сохранённый объект TextMessage. Его поле data.text является точкой внедрения (помечено ключами _injection_point / _note, поскольку в JSON нет комментариев). Вектор внедрения (``) идентичен раскрытому в отчёте; действие изменено на безвредный маркер на странице.
payload.txt — то же внедрение, задокументированное в виде простого текста, включая исходный сформированный запрос, с явным примечанием, что он никуда не отправляется.
Этот repro.html — минимальная заглушка, написанная на основе механизма из описания. Это не фрагмент исходного кода вендора; настоящий SDK не был получен или дизассемблирован для этого воспроизведения.
Полезная нагрузка в этом воспроизведении безвредна: вместо alert(document.domain) обработчик onerror устанавливает window.__xss_fired = true, записывает RESULT: XSS-FIRED на страницу и задаёт заголовок документа. Ничего не эксфильтруется.
cd poc/CVE-2026-39154
python3 -m http.server 5599
# then open http://localhost:5599/repro.html in a browser
repro.html синхронно отображает встроенную копию сохранённого сообщения (так что
сток срабатывает детерминированно даже при headless-захвате) и дополнительно загружает
payload.json, чтобы подтвердить, что артефакт на диске загружается и содержит то же поле.
RESULT: XSS-FIRED красным — доказательство того, что
внедрённый скрипт onerror выполнился через несанитизированный сток innerHTML.data.text был бы закодирован и отобразился бы как
буквальный текст `` без выполнения скрипта.payload.json
был проверен как корректный JSON и подтверждён как содержащий инъекцию в
data.text (вектор ). Каталог был отдан через `python3 -m http.server 5599`, а `repro.html` был загружен в **Firefox 140.11.0esr headless** через `--screenshot` (сохранён здесь как `repro-screenshot.png`). Захваченный скриншот показывает, что отрисован как элемент битого изображения (т. е. data.text был разобран как HTML,
а не показан как текст), и RESULT: XSS-FIRED записан в DOM. Это подтверждает,
что несанитизированный сток innerHTML выполняет внедрённый скрипт из поля сообщения./v3.0/messages, не использовались
ни аккаунт, ни группа, и никакая реальная сборка SDK не скачивалась, не выполнялась и не дизассемблировалась.
Сток здесь — это точная рукописная заглушка, основанная на описанном в отчёте
механизме, а не на поставляемом вендором коде. Точная версия исправленного SDK не была
подтверждена, поскольку в отчёте она не указана.Это локальное офлайн-воспроизведение с использованием только синтетических, самостоятельно сгенерированных данных, созданное для документирования уже отчёченного и исправленного вендором CVE. Оно нацелено на локальную заглушку уязвимого пути рендеринга, а не на какую-либо живую или стороннюю систему. Полезная нагрузка — безвредный маркер на странице; она не содержит эксфильтрации или оружизации сверх демонстрации того, что поле рендерится без санитизации.