
CVE-2026-39154,CometChat JS SDK 中的存储型 XSS
本目录包含对 CVE-2026-39154 背后**渲染汇聚点(render sink)**的诚实、自包含复现:这是 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 |
| 交互 | 无;当成员打开该群组时,存储的载荷会自动执行 |
| 披露 | 于 2026 年 2 月报告给 CometChat(请求 42660);厂商标记为已解决;CVE 由 MITRE 分配 |
| 参考 | https://www.cve.org/CVERecord?id=CVE-2026-39154 |
关于修复版本的说明:已发布的分析文章指出厂商已解决该问题(“VENDOR: SOLVED”),但未指明具体的已修复 SDK/构建版本号,且除上述 CVE 记录外未给出单独的厂商公告 URL。此处不主张任何修复版本号,因为分析文章未提供。
CometChat 的 TextMessage 将其文本承载于 data.text 中,并以 message.getText() 的形式暴露给 UI 代码。已认证的成员可以向群组发送一条消息,其 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 渲染路径。它包含一份未净化 sink 的忠实本地副本:
function getText(message){ return message.data.text; } // mirrors TextMessage#getText()
...
body.innerHTML = getText(message); // *** the SDK 3.0.12 sink, reproduced ***
它通过该 sink 渲染一个合成的已存储消息对象,使注入的脚本在本地执行。页面上有一段内联的诚实说明,指出这是对 SDK 3.0.12 sink 的本地复现,修复后的 SDK 会对该字段进行净化处理,且不涉及任何真实的 CometChat 服务。
payload.json —— 合成的已存储 TextMessage 对象。其 data.text 字段是注入点(由于 JSON 不支持注释,因此用 _injection_point / _note 键进行标注)。注入向量(``)与已披露的报告完全相同;动作则改为无害的页面内标记。
payload.txt —— 以纯文本形式记录的同一注入,包括原始的构造请求,并明确注明它不会被发送到任何地方。
这个 repro.html 是根据技术文章中描述的机制编写的最小替代实现。它并非厂商源代码的摘录;本次复现并未获取或反汇编真实的 SDK。
本次复现中的 payload 是无害的:onerror 处理程序不会执行 alert(document.domain),而是设置 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 会同步渲染所存储消息的内嵌副本(因此即使在无头捕获下,sink 也会确定性地触发),并额外获取 payload.json,以确认磁盘上的工件能够加载并携带相同字段。
RESULT: XSS-FIRED —— 这证明注入的 onerror 脚本通过未经过滤的 innerHTML sink 执行了。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 sink 会执行来自消息字段的注入脚本。/v3.0/messages 发送任何内容,未使用任何账户或群组,也未下载、执行或反汇编任何真实 SDK 构建。此处的 sink 是基于该文章所述机制忠实手写的替代实现,而不是供应商发布的代码。确切的修复后 SDK 版本未得到确认,因为该文章未说明具体版本。这是一个本地、离线的复现,仅使用合成的、自行生成的数据,旨在记录一个已被报告且供应商已解决的 CVE。它针对的是易受攻击渲染路径的本地替代实现,而不是任何实时或第三方系统。该 payload 是一个无害的页面内标记;除了证明该字段在渲染时未经过滤之外,它不包含任何数据外泄或武器化内容。