Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2026-66418-OpenClaw-Dashboard-v3.0.0-Stored-XSS-via-Failed-Login-Username-Field — Avis de sécurité : Cross-Site Scripting stocké non authentifié menant à la prise de contrôle du compte administrateur (openclaw-dashboard) | Kitploit
Outils/GitHubGitHub/theopaid/cve-2026-66418-openclaw-dashboard-v3.0.0-stored-xss-via-failed-login-username-field
Analyse des VulnérabilitésExploitation d'Applications WebSécurité WebApprentissage et Éducation
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

Avis de sécurité : Cross-Site Scripting stocké non authentifié menant à la prise de contrôle du compte administrateur (openclaw-dashboard)

Voir le dépôt

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager
il y a 18 joursPas encore vérifié

Avis de sécurité : Cross-Site Scripting stocké non authentifié menant à la prise de contrôle du compte administrateur (openclaw-dashboard)

Titre : XSS stocké dans OpenClaw Dashboard v3.0.0 via le champ de nom d'utilisateur d'une connexion échouée Identifiant CVE attribué : CVE-2026-66418

Dépôt cible : https://github.com/tugcantopaloglu/openclaw-dashboard

Résumé

Le endpoint de connexion enregistre le nom d'utilisateur soumis dans le journal d'audit sans aucune validation. Le panneau de notifications lit ensuite ces entrées de journal et les écrit dans la page avec innerHTML et sans échappement. Un attaquant qui ne peut pas se connecter peut néanmoins envoyer une requête de connexion échouée dont le nom d'utilisateur est une charge utile de script. La prochaine fois que l'administrateur connecté ouvre la cloche de notifications, cette charge utile s'exécute dans son navigateur, dans l'origine du tableau de bord, avec accès à son jeton de session.

  • CWE-79 : Neutralisation incorrecte de l'entrée lors de la génération de pages Web (Cross-site Scripting)
  • CWE-117 : Mauvaise neutralisation de la sortie pour les journaux
  • CVSS 4.0 : 9.3 (Critique). Vecteur : 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

Versions concernées

Introduit dans la v3.0.0, qui a ajouté le centre de notifications qui affiche les entrées du journal d'audit. Présent dans la v3.0.0 et dans chaque commit ultérieur jusqu'au main actuel inclus (d6198d0). Non corrigé au moment de la rédaction.

Modèle de menace

L'attaquant est un tiers distant qui peut atteindre le port HTTP du tableau de bord mais ne possède ni compte ni identifiants valides. Cela correspond à un écran de connexion accessible à quiconque peut ouvrir le tableau de bord. La seule condition préalable est qu'un compte administrateur existe déjà, ce qui est vrai pour tout déploiement après la configuration initiale.

La charge utile est stockée, donc aucune coordination temporelle n'est nécessaire. Elle s'exécute lorsque l'administrateur ouvre le panneau de notifications, une action normale proposée par l'interface. Une fois exécutée, elle dispose des mêmes capacités que le navigateur de l'administrateur : elle peut lire le jeton de session et appeler tout endpoint authentifié au nom de la victime.

Cause racine

Étape 1. Le nom d'utilisateur est journalisé tel quel. Une connexion avec un nom d'utilisateur qui ne correspond pas au compte enregistré atteint cette branche :

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 provient directement du corps de la requête JSON. Il n'y a aucune limite de longueur, aucune liste blanche de caractères ni aucun contrôle de type. auditLog l'écrit sur le disque sous forme de ligne JSON :

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 échappe les guillemets et les sauts de ligne, de sorte que la charge utile reste sur une seule ligne et se réanalyse proprement. Il n'échappe pas <, > ni /, donc le balisage HTML survit intact.

Étape 2. Le journal est relu. Le endpoint de notifications renvoie les lignes de journal récentes au navigateur :

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 }));

Étape 3. Le nom d'utilisateur est écrit dans le DOM sans échappement. Le frontend construit chaque ligne de notification par concaténation de chaînes et l'affecte avec 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 est la chaîne de l'attaquant. Elle atteint innerHTML sans encodage, de sorte que le navigateur l'analyse comme du HTML.

La Content-Security-Policy définie dans server.js:298 inclut script-src 'self' 'unsafe-inline', donc les gestionnaires d'événements en ligne tels que onerror sont autorisés à s'exécuter.

Preuve de concept

  1. Assurez-vous qu'un compte administrateur existe (toute installation normale). L'attaquant n'a pas besoin de ses identifiants.

  2. En tant qu'attaquant non authentifié, envoyez une connexion échouée dont le nom d'utilisateur est la charge utile :

    root@kitploit:~
    curl -X POST http://TARGET:7000/api/auth/login \
      -H 'Content-Type: application/json' \
      -d '{"username":"","password":"x"}'
    
  3. Le serveur répond 401 Invalid username or password et stocke la charge utile.

  4. L'administrateur se connecte normalement et clique sur la cloche de notifications.

  5. La charge utile s'exécute dans la session de l'administrateur. Dans cet exemple, elle lit le jeton de session avec le getStoredToken() de la page et l'utilise pour écraser le fichier d'instructions de l'agent AGENTS.md via POST /api/key-file. Tout endpoint authentifié peut être appelé de la même manière.

Le texte de la charge utile reste sur une seule ligne de journal et revient octet pour octet via /api/notifications, de sorte que l'injection et l'exécution peuvent être confirmées séparément.

Impact

Exécution de code dans l'origine du tableau de bord en tant qu'administrateur. Le script peut lire le jeton de session et émettre toute requête authentifiée, y compris modifier les fichiers d'instructions et de compétences de l'agent et changer la configuration d'OpenClaw. Comme l'attaquant n'a besoin d'aucun compte, cela transforme une requête réseau non authentifiée en contrôle de la session de l'administrateur.

Remédiation

  • Encodez en HTML chaque valeur avant de la placer dans innerHTML, ou construisez les nœuds avec textContent au lieu d'une concaténation de chaînes. Les champs nom d'utilisateur, événement et IP des notifications en ont tous besoin.
  • Validez username côté serveur avant de le journaliser : limitez sa longueur et restreignez-le à un jeu de caractères attendu.
  • Retirez 'unsafe-inline' de script-src. Avec les gestionnaires en ligne bloqués, ce problème passe d'une exécution de code à un balisage inoffensif.
Télécharger l’outil