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 '';
}).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'`。在阻止内联处理器后,此问题将从代码执行降级为无害的标记。
' + (e.event||'').replace(/_/g, ' ') + detail + ip + '
' + time + '
标签:Homebrew安装, MITM代理, Python脚本, Web组件, 多模态安全, 安全漏洞, 数据可视化, 漏洞披露, 管理面板, 跨站脚本(XSS)