
Preuve de concept en laboratoire pour une faille d'autorisation dans Apache Superset 6.1.0, où un éditeur de tableau de bord Gamma injecte du SQL de jeu de données via l'import de tableau de bord v0 non versionné, exécuté lors du rafraîchissement du graphique.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · superset-g3-v0-import-sql-plant
Un collègue disposant d'un compte normal d'éditeur de tableau de bord peut dissimuler sa propre requête de base de données à l'intérieur d'un graphique auquel d'autres personnes font déjà confiance. La prochaine fois que quelqu'un ouvre ce graphique, la requête s'exécute sur la connexion aux données de l'entreprise. Il peut lire des tables auxquelles il n'a jamais eu accès. Il n'a pas besoin de la permission supplémentaire « exécuter mon propre SQL » censée contrôler cela. Il ne peut pas enregistrer un nouvel identifiant d'entrepôt de données, et il ne peut pas exécuter INSERT ou DELETE via ce chemin de graphique.
Classe : Privilège résiduel Portée : Distante
Apache Superset 6.1.0 (c83fb2bb) - Apache Software Foundation
Le Gamma par défaut possède can_write sur Dashboard, donc POST /api/v1/dashboard/import/ est dans la matrice. Un JSON non-ZIP n'a pas de metadata.yaml, donc le répartiteur essaie v1, puis passe à la version non versionnée v0. decode_dashboards reconstitue SqlaTable. import_dataset recherche par table_name + schema + database_id (depuis params.database_name) et datasource.override(i_datasource) copie export_fields y compris sql sans raise_for_access et sans écriture sur le Dataset. POST /api/v1/chart/data compile ensuite ce SQL comme FROM virtuel sur la connexion analytique du dataset.
SQL Gamma sans sql_lab. Le PUT REST du Dataset reste en 403. Le ZIP v1 reste vide.
| ID | pas encore de CVE |
| Classe | Privilège résiduel (résidu de CVE-2026-23982 / CVE-2024-26016) |
| Portée | Distante (HTTP authentifié ; Gamma par défaut) |
| CWE | CWE-863, CWE-284 |
| CVSS | Élevé : 7.6 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L |
| Produit | Apache Superset |
| Affecté | 6.1.0 (c83fb2bb1dcfac41ac51bcebd82471f4a7180d18) ; dernière version GA de GitHub au moment de la chasse |
| Auth | Gamma par défaut (can_write sur Dashboard). Pas non authentifié. Pas Admin (hors périmètre). |
| Licence | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 uniquement |
Nécessite un compte Gamma (ou tout compte avec écriture sur Dashboard) et un dataset qu'il pourra ensuite utiliser dans un graphique : même table_name + schema sur une Database dont le database_name figure dans les params du JSON. L'autorisation complémentaire porte sur l'objet dataset, et non sur chaque table nommée par le SQL implanté. Le raise_for_access de chart/data porte sur l'objet datasource (datasource_access / schema / propriétaire). get_from_clause ne refuse que has_mutation().
| Privilège | Méthode / sink | Reconnaissance / intégrité / disponibilité | Prouvé en labo ou déduit |
|---|---|---|---|
| Écraser le SQL virtuel d'un dataset correspondant à un nom | Gamma POST /api/v1/dashboard/import/ JSON non-ZIP ; v0 import_dataset datasource.override copie export_fields y compris sql | Intégrité (dataset.sql) | Prouvé en labo. Import 200 ; dataset.sql = SELECT 'SUPERSET-G3-V0-IMPORT-SQL-PLANT-WITNESS' AS witness |
| Exécuter ce SQL sur l'URI analytique du dataset | POST /api/v1/chart/data après l'implantation ; get_from_clause enveloppe le sql stocké en TextAsFrom | Reconnaissance (SELECT sur toute table que l'utilisateur de l'URI peut lire) | Prouvé en labo. 200 ; FROM (SELECT 'SUPERSET-G3-V0-IMPORT-SQL-PLANT-WITNESS' AS witness) AS virtual_table compilé |
| Cacher la requête dans un graphique que d'autres utilisateurs autorisés ont déjà ouvert | Même implantation ; les propriétaires / autres principaux datasource_access exécutent au rafraîchissement | Intégrité + reconnaissance (d'autres personnes exécutent l'implantation) | Déduit. Le labo Gamma disposait déjà de l'autorisation, donc auto-exécuté |
| Casser les tableaux de bord qui utilisaient l'ancien SQL | Remplacer le sql de la table physique par un SELECT de l'attaquant | Disponibilité | Déduit |
| Négatif : exécution SQL Lab | POST /api/v1/sqllab/execute/ | s.o. | Prouvé en labo. 403 |
| Négatif : écrasement REST du Dataset | PUT /api/v1/dataset/{id} avec sql | s.o. | Prouvé en labo. 403 ; sql resté vide (sibling de CVE-2026-23982) |
Négatif : ZIP v1 avec metadata.yaml | même endpoint d'import, bundle ZIP | s.o. | Prouvé en labo. 200 ; sql resté vide (classe CVE-2024-26016) |
Clos sur ce reliquat : import non authentifié ; enregistrement d'une nouvelle URI de Database (l'import de dataset v0 ne crée pas la connexion ; l'absence de params.database_name donne DatabaseNotFoundError) ; DML via chart/data (has_mutation()) ; Gamma cartographiant un dataset qu'il ne peut pas lire (403) ; un table_name unique nouvellement inséré (PVM créé, non attaché à Gamma). Pas de RCE. Pas d'Admin. Le cloisonnement de l'entrepôt de production limite le SELECT à l'utilisateur de cette URI. L'URI analytique du labo était le même Postgres que les métadonnées, donc le cross-table incluant ab_user est déduit, pas vérifié par un second oracle.
J'ai épinglé Apache Superset 6.1.0. Admin est équivalent à l'OS et hors périmètre. J'ai traqué les reliquats face à la matrice de rôles d'Apache elle-même : Gamma, Alpha, sql_lab, invité embarqué, Public.
La vague 6.0.0 avait déjà fermé sqlExpression de chart/data (CVE-2026-23980), le SQL REST du Dataset (CVE-2026-23982), et DO $$ / EXPLAIN ANALYZE de SQL Lab (CVE-2026-23984). L'autorisation d'import pour le ZIP versionné est CVE-2024-26016. Le v0 non versionné reste en dernier dans command_versions car « les fichiers ne sont pas versionnés ».
import_ sur Dashboard correspond à l'écriture. Le JSON non-ZIP est {filename: bytes} sans metadata.yaml. Le dispatcher essaie v1, intercepte IncorrectVersionError, exécute v0. decode_dashboards transforme __SqlaTable__ en modèle vivant. import_dataset recherche et écrase. export_fields inclut sql. Au moment de la requête, le SQL stocké est traité comme fiable : get_from_clause après has_mutation().
Fausses pistes déjà consignées : traiter le ZIP v1 comme le reliquat (sql resté vide) ; PUT REST du Dataset (403) ; SQL Lab en tant que Gamma (403) ; gonfler vers un objet Database quelconque (v0 n'enregistre pas d'URI) ; un reverse shell. Du théâtre. Le témoin, c'est le FROM virtuel.
python3 superset-g3-v0-import-sql-plant-Abraxas-Labs.py
ou :
cd lab
./run.sh