Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-66418-OpenClaw-Dashboard-v3.0.0-Stored-XSS-via-Failed-Login-Username-Field — Security Advisory: Unauthenticated Stored Cross-Site Scripting Leading To Administrator Account Takeover (openclaw-dashboard) | Kitploit
Tools/GitHubGitHub/theopaid/cve-2026-66418-openclaw-dashboard-v3.0.0-stored-xss-via-failed-login-username-field
Vulnerability AnalysisWeb Application ExploitationWeb SecurityLearning & Education
GitHubtheopaid/cve-2026-66418-openclaw-dashboard-v3.0.0-stored-xss-via-failed-login-username-field

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

Security Advisory: Unauthenticated Stored Cross-Site Scripting Leading To Administrator Account Takeover (openclaw-dashboard)

View Repository

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share
18 days agoNot yet reviewed

Security Advisory: Unauthenticated Stored Cross-Site Scripting Leading To Administrator Account Takeover (openclaw-dashboard)

Title: OpenClaw Dashboard v3.0.0 Stored XSS via Failed Login Username Field Assigned CVE ID: CVE-2026-66418

Target Repository: https://github.com/tugcantopaloglu/openclaw-dashboard

Summary

The login endpoint records the submitted username in the audit log without any validation. The notification panel later reads those log entries and writes them into the page with innerHTML and no escaping. An attacker who cannot log in can still send a failed login request whose username is a script payload. The next time the logged-in administrator opens the notification bell, that payload runs in their browser, in the dashboard's origin, with access to their session token.

  • CWE-79: Improper Neutralization of Input During Web Page Generation (Cross-site Scripting)
  • CWE-117: Improper Output Neutralization for Logs
  • CVSS 4.0: 9.3 (Critical). Vector: 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

Affected versions

Introduced in v3.0.0, which added the notification center that renders audit-log entries. Present in v3.0.0 and every later commit up to and including the current main (d6198d0). Not fixed at the time of writing.

Threat model

The attacker is a remote party who can reach the dashboard's HTTP port but has no account and no valid credentials. This matches the login screen being reachable by anyone who can open the dashboard. The only precondition is that an administrator account already exists, which is true for any deployment past first-run setup.

The payload is stored, so no timing coordination is needed. It executes when the administrator opens the notification panel, which is a normal action offered by the UI. Once it runs, it has the same abilities as the administrator's browser: it can read the session token and call any authenticated endpoint on the victim's behalf.

Root cause

Step 1. The username is logged verbatim. A login with a username that does not match the registered account reaches this branch:

root@kitploit:~
// 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 comes straight from the JSON request body. There is no length limit, character allowlist, or type check. auditLog writes it to disk as a JSON line:

root@kitploit:~
// 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 escapes quotes and newlines, so the payload stays on one line and parses back cleanly. It does not escape <, >, or /, so HTML markup survives intact.

Step 2. The log is read back. The notifications endpoint returns recent log lines to the browser:

root@kitploit:~
// 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 }));

Step 3. The username is written into the DOM without escaping. The frontend builds each notification row by string concatenation and assigns it with innerHTML:

root@kitploit:~
// 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 '<div class="notif-item"><div class="notif-icon">' + icon + '</div><div class="notif-content"><div class="notif-event">' + (e.event||'').replace(/_/g, ' ') + detail + ip + '</div><div class="notif-time">' + time + '</div></div></div>';
}).join('');

e.username is the attacker's string. It reaches innerHTML with no encoding, so the browser parses it as HTML.

The Content-Security-Policy set at server.js:298 includes script-src 'self' 'unsafe-inline', so inline event handlers such as onerror are allowed to run.

Proof of concept

  1. Make sure an administrator account exists (any normal install). The attacker does not need its credentials.

  2. As the unauthenticated attacker, send one failed login whose username is the payload:

    root@kitploit:~
    curl -X POST http://TARGET:7000/api/auth/login \
      -H 'Content-Type: application/json' \
      -d '{"username":"","password":"x"}'
    

    The server replies 401 Invalid username or password and stores the payload.

  3. The administrator logs in normally and clicks the notification bell.

  4. The payload runs in the administrator's session. In this example it reads the session token with the page's own getStoredToken() and uses it to overwrite the agent instruction file AGENTS.md through POST /api/key-file. Any authenticated endpoint can be called the same way.

The payload text stays a single log line and comes back byte-for-byte through /api/notifications, so the injection and the execution can be confirmed separately.

Impact

Code execution in the dashboard origin as the administrator. The script can read the session token and issue any authenticated request, including editing the agent's instruction and skill files and changing the OpenClaw configuration. Because the attacker needs no account, this turns an unauthenticated network request into control of the administrator's session.

Remediation

  • HTML-encode every value before placing it in innerHTML, or build nodes with textContent instead of string concatenation. The notification username, event, and IP fields all need this.
  • Validate username on the server before logging it: cap its length and restrict it to an expected character set.
  • Remove 'unsafe-inline' from script-src. With inline handlers blocked, this issue drops from code execution to harmless markup.
Download Tool