theopaid/CVE-2026-66418-OpenClaw-Dashboard-v3.0.0-Stored-XSS-via-Failed-Login-Username-Field

GitHub: theopaid/CVE-2026-66418-OpenClaw-Dashboard-v3.0.0-Stored-XSS-via-Failed-Login-Username-Field

该仓库是一份针对 OpenClaw Dashboard v3.0.0 的安全漏洞公告,详细披露了通过失败登录用户名字段注入存储型 XSS 以接管管理员账户的漏洞链与 PoC。

Stars: 0 | Forks: 0

# 安全通告:未经身份验证的存储型跨站脚本攻击导致管理员账户接管 (openclaw-dashboard) 标题:OpenClaw Dashboard v3.0.0 通过失败登录用户名字段引发的存储型 XSS 分配的 CVE ID:CVE-2026-66418 目标代码库:https://github.com/tugcantopaloglu/openclaw-dashboard ## 摘要 登录 endpoint 在未进行任何验证的情况下,将提交的用户名记录在审计日志中。随后,通知面板会读取这些日志条目,并通过 `innerHTML` 将其未转义地写入页面。无法登录的攻击者仍然可以发送一个用户名为 script payload 的失败登录请求。当下次已登录的管理员打开通知铃铛时,该 payload 就会在其浏览器中执行,运行于 dashboard 的 origin 下,并获取其 session token。 - CWE-79:Web 页面生成时对输入的不当中和处理(跨站脚本攻击) - CWE-117:日志的不当输出中和 - CVSS 4.0:9.3(严重)。向量:`CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:H/VI:H/VA:N/SC:H/SI:H/SA:N` ## 受影响版本 引入于 v3.0.0 版本,该版本添加了用于渲染审计日志条目的通知中心。存在于 v3.0.0 及其之后的每一次 commit,直至并包含当前的 `main` 分支 (d6198d0)。在撰写本文时尚未修复。 ## 威胁模型 攻击者是一个可以访问 dashboard 的 HTTP 端口,但没有账户和有效凭证的远程方。这与任何能够打开 dashboard 的人都可以访问登录界面的情况相符。唯一的前提是已经存在管理员账户,这对于任何完成首次运行的部署都是成立的。 由于 payload 是被存储的,因此不需要进行时间上的协调。当管理员打开通知面板(这是 UI 提供的一项正常操作)时,它就会执行。一旦运行,它就拥有了与管理员浏览器相同的能力:它可以读取 session token 并代表受害者调用任何经过身份验证的 endpoint。 ## 根本原因 **步骤 1. 用户名被原样记录。** 使用与已注册账户不匹配的用户名进行登录会进入此分支: ``` // server.js:1577-1583 if (username !== creds.username) { recordFailedAuth(ip); auditLog('login_failed', ip, { username }); res.writeHead(401, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Invalid username or password' })); return; } ``` `username` 直接来自 JSON 请求体。没有长度限制、字符允许列表或类型检查。`auditLog` 将其作为 JSON 行写入磁盘: ``` // server.js:278-282 function auditLog(event, ip, details = {}) { try { const timestamp = new Date().toISOString(); const entry = JSON.stringify({ timestamp, event, ip, ...details }) + '\n'; fs.appendFileSync(auditLogPath, entry, 'utf8'); ``` `JSON.stringify` 会转义引号和换行符,因此 payload 保留在一行内并能被干净地解析回来。它不会转义 `<`、`>` 或 `/`,因此 HTML 标记可以完整保留。 **步骤 2. 日志被重新读取。** 通知 endpoint 将最近的日志行返回给浏览器: ``` // server.js:2042-2051 if (req.url.startsWith('/api/notifications')) { if (!requireAuth(req, res)) return; const limit = parseInt(new URL(req.url, 'http://localhost').searchParams.get('limit') || '50'); try { const raw = fs.readFileSync(auditLogPath, 'utf8').trim(); const lines = raw.split('\n').filter(Boolean).slice(-Math.min(limit, 200)); const events = lines.map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean).reverse(); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ events })); ``` **步骤 3. 用户名在未转义的情况下被写入 DOM。** 前端通过字符串拼接构建每个通知行,并使用 `innerHTML` 进行赋值: ``` // index.html:5647-5653 body.innerHTML = data.events.map(e => { const icon = notifIcons[e.event] || '📋'; const time = e.timestamp ? new Date(e.timestamp).toLocaleString() : ''; const detail = e.username ? ' (' + e.username + ')' : ''; const ip = e.ip ? ' from ' + e.ip : ''; return '
' + icon + '
' + (e.event||'').replace(/_/g, ' ') + detail + ip + '
' + time + '
'; }).join(''); ``` `e.username` 是攻击者提供的字符串。它在未进行编码的情况下到达 `innerHTML`,因此浏览器会将其解析为 HTML。 server.js:298 处设置的 Content-Security-Policy 包含 `script-src 'self' 'unsafe-inline'`,因此像 `onerror` 这样的内联事件处理器被允许运行。 ## 概念验证 1. 确保存在管理员账户(任何正常的安装皆是如此)。攻击者不需要其凭证。 2. 作为未经身份验证的攻击者,发送一个用户名为 payload 的失败登录请求: curl -X POST http://TARGET:7000/api/auth/login \ -H 'Content-Type: application/json' \ -d '{"username":"","password":"x"}' 服务器回复 `401 Invalid username or password` 并存储了该 payload。 3. 管理员正常登录并点击通知铃铛。 4. payload 在管理员的会话中执行。在此示例中,它使用页面自身的 `getStoredToken()` 读取 session token,并利用它通过 `POST /api/key-file` 覆盖 agent 指令文件 `AGENTS.md`。任何经过身份验证的 endpoint 都可以通过相同的方式调用。 payload 文本保持为单行日志,并通过 `/api/notifications` 原封不动地返回,因此注入和执行可以分别进行验证。 ## 影响 以管理员身份在 dashboard origin 中执行代码。该 script 可以读取 session token 并发出任何经过身份验证的请求,包括编辑 agent 的指令和技能文件,以及更改 OpenClaw 配置。由于攻击者不需要账户,这将一个未经身份验证的网络请求转化为对管理员会话的控制。 ## 修复建议 - 在将每个值放入 `innerHTML` 之前对其进行 HTML 编码,或者使用 `textContent` 构建节点而不是进行字符串拼接。通知的用户名、事件和 IP 字段都需要进行此操作。 - 在服务器端记录 `username` 之前对其进行验证:限制其长度并将其限制为预期的字符集。 - 从 `script-src` 中移除 `'unsafe-inline'`。在阻止内联处理器后,此问题将从代码执行降级为无害的标记。
标签:Homebrew安装, MITM代理, Python脚本, Web组件, 多模态安全, 安全漏洞, 数据可视化, 漏洞披露, 管理面板, 跨站脚本(XSS)