theopaid/CVE-2026-66421-OpenClaw-Dashboard-Stored-XSS-via-lastMessage-Session-Field

GitHub: theopaid/CVE-2026-66421-OpenClaw-Dashboard-Stored-XSS-via-lastMessage-Session-Field

该仓库是 OpenClaw Dashboard 存储型 XSS 漏洞(CVE-2026-66421)的安全通告,详细分析了通过 agent 消息注入实现 session token 窃取的攻击路径与修复方案。

Stars: 0 | Forks: 0

# 安全通告:通过 Agent 消息引发的存储型跨站脚本攻击导致 Session Token 窃取 (openclaw-dashboard) 标题:OpenClaw Dashboard 通过 lastMessage Session 字段引发的存储型 XSS CVE ID:CVE-2026-66421 https://github.com/tugcantopaloglu/openclaw-dashboard ## 概述 Dashboard 首页上的 sessions 表显示了每个 agent session 的最后一条消息。该文本取自 agent 的对话记录,并通过 `innerHTML` 在未经转义的情况下写入页面。OpenClaw 是一个多渠道 agent 网关,因此 消息文本可以来自任何能够与 agent 对话的人,例如聊天 群组或 webhook。包含 HTML payload 的消息会在 管理员打开 dashboard 时立即在其浏览器中作为脚本运行。 - CWE-79:网页生成时对输入的不正确中和(跨站脚本攻击) - CVSS 4.0:8.8(高)。向量:`CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:N/SC:H/SI:H/SA:N` ## 受影响版本 自首次公开发布以来便已存在。该漏洞渲染存在于最早的 tag (v1.1.0) 以及随后的每一次 commit 中,直至并包含当前的 `main` (d6198d0)。在撰写本文时尚未修复。 ## 威胁模型 攻击者是能够发送消息并使其进入 agent session 记录的一方。 他们不需要拥有 Dashboard 账号。在典型的 OpenClaw 部署中,这 包括已连接的聊天频道的成员,以及任何通过 webhook 到达 agent 的来源,因此攻击者通常处于运营者的信任边界之外。 payload 被存储在记录中,并在管理员查看 sessions 页面时被渲染。该页面是默认的登录视图,并且会定时刷新, 因此所需的唯一交互就是管理员加载他们通常使用的 dashboard。`AT:P` 反映了攻击者所依赖的一个条件:即部署环境 将他们的消息路由到了 Dashboard 显示的 session 中。 ## 根本原因 **第 1 步。消息文本从记录中提取。** 服务器读取 最近的消息并返回其文本,截断为 80 个字符,未进行任何清理: ``` // server.js:474-481 if (typeof msg.content === 'string') { text = msg.content; } else if (Array.isArray(msg.content)) { for (const b of msg.content) { if (b.type === 'text' && b.text) { text = b.text; break; } } } if (text) return text.replace(/\n/g, ' ').substring(0, 80); ``` **第 2 步。它通过 sessions API 暴露。** `getSessionsJson` 将文本放入 每个 session 的 `lastMessage` 字段中: ``` // server.js:538 lastMessage: getLastMessage(s.sessionId || key), ``` `GET /api/sessions` 将此数组返回给浏览器。 **第 3 步。它在未经转义的情况下被写入 DOM。** sessions 表通过 字符串拼接构建每一行,并使用 `innerHTML` 进行赋值: ``` // index.html:3758 const lastMsg = s.lastMessage ? s.lastMessage.substring(0, 60) + (s.lastMessage.length > 60 ? '…' : '') : ''; ``` ``` // index.html:3773
${lastMsg}
``` `lastMsg` 被直接放入 HTML 字符串中。在此路径的任何地方都没有 编码步骤。 同一个表以相同的方式将 `s.label` (index.html:3770) 和 `s.key` (index.html:3767) 插入到行 markup 中。这些字段没有 60 个字符的限制,因此能够影响它们的 攻击者有更多的空间来放置 payload。 server.js:298 处的 Content-Security-Policy 允许内联事件处理程序 (`script-src 'self' 'unsafe-inline'`),因此 `onerror` 处理程序可以运行。 ## 概念验证 1. 作为可以向 agent 发送消息的一方,发送一条文本为简短 payload 的消息 (该 payload 需符合 60 个字符的预算限制),例如: 这是 56 个字符,会像任何正常 消息一样到达 agent 的 session 记录。 2. 确认服务器未经转义便将 payload 返回: curl -H "Authorization: Bearer ADMIN_TOKEN" http://TARGET:7000/api/sessions 受影响 session 的 `lastMessage` 字段包含原始的 `` 字符串。 3. 管理员打开 Dashboard。sessions 页面是默认视图,因此不需要 额外的导航。行被渲染,浏览器解析 `` 标签, `onerror` 处理程序运行,管理员的 session token 被 payload 读取。 将 `console.log` 函数体替换为向同源已验证 endpoint 发起的 `fetch`,可让 payload 以 已登录用户的权限执行操作。 ## 影响 以已登录用户身份在 Dashboard 源中执行脚本,且只需向 agent 发送一条消息即可触达。payload 可以读取 session token 并调用 已验证的 endpoint,包括那些用于编辑 agent 自身指令文件和 OpenClaw 配置的 endpoint。因为它在默认页面上触发,管理员 不必采取任何异常操作。 ## 修复建议 - 在将 `lastMessage`、`label` 和 `key` 插入行 markup 之前对其进行 HTML 编码,或者使用 `textContent` 而不是 HTML 字符串来构建单元格。 - 对通过 `innerHTML` 渲染的其他 session 字段应用相同的编码。 - 从 `script-src` 中移除 `'unsafe-inline'`,这样即使漏掉了 某个编码步骤,注入的 markup 也无法执行。
标签:Agent网关, MITM代理, Web前端, XSS漏洞, 多模态安全, 安全公告, 数据可视化, 管理面板