
Proof-of-concept lab for an Apache Superset 6.1.0 authorization flaw where a Gamma dashboard editor plants dataset SQL via unversioned v0 dashboard import, executed on chart refresh.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · superset-g3-v0-import-sql-plant
A coworker with a normal dashboard-editor account can hide their own database query inside a chart other people already trust. The next time anyone opens that chart, the query runs against the company's data connection. They can read tables they were never given access to. They do not need the extra "run my own SQL" permission that is supposed to gate this. They cannot register a new warehouse login, and they cannot run INSERT or DELETE through this chart path.
Class: Privilege leftover Reach: Remote
Apache Superset 6.1.0 (c83fb2bb) - Apache Software Foundation
Default Gamma has can_write Dashboard, so POST /api/v1/dashboard/import/ is in-matrix. Non-ZIP JSON has no metadata.yaml, so the dispatcher tries v1, then falls through to unversioned v0. decode_dashboards reconstitutes SqlaTable. import_dataset looks up by table_name + schema + database_id (from params.database_name) and datasource.override(i_datasource) copies export_fields including sql with no raise_for_access and no Dataset write. POST /api/v1/chart/data then compiles that SQL as the virtual FROM on the dataset's analytics connection.
Gamma SQL without sql_lab. Dataset REST PUT stays 403. v1 ZIP stays empty.
| ID | no CVE yet |
| Class | Privilege leftover (leftover of CVE-2026-23982 / CVE-2024-26016) |
| Reach | Remote (authenticated HTTP; default Gamma) |
| CWE | CWE-863, CWE-284 |
| CVSS | High: 7.6 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L |
| Product | Apache Superset |
| Affected | 6.1.0 (c83fb2bb1dcfac41ac51bcebd82471f4a7180d18); GitHub Latest GA at hunt |
| Auth | default Gamma (can_write Dashboard). Not unauth. Not Admin (out of scope). |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 only |
Needs a Gamma (or any Dashboard-write) account and a dataset they can later chart: same table_name + schema on a Database whose database_name is in the JSON params. Complementary grant is on the dataset object, not every table the planted SQL names. Chart/data raise_for_access is datasource-object (datasource_access / schema / owner). get_from_clause only refuses has_mutation().
| Privilege | Method / sink | Recon / integrity / availability | Lab-proven or inferred |
|---|---|---|---|
| Overwrite virtual SQL of a name-matched dataset | Gamma POST /api/v1/dashboard/import/ non-ZIP JSON; v0 import_dataset datasource.override copies export_fields including sql | Integrity (dataset.sql) | Lab-proven. Import 200; dataset.sql = SELECT 'SUPERSET-G3-V0-IMPORT-SQL-PLANT-WITNESS' AS witness |
| Run that SQL on the dataset's analytics URI | POST /api/v1/chart/data after plant; get_from_clause wraps stored sql as TextAsFrom | Recon (SELECT any table that URI user can read) | Lab-proven. 200; compiled FROM (SELECT 'SUPERSET-G3-V0-IMPORT-SQL-PLANT-WITNESS' AS witness) AS virtual_table |
| Hide the query in a chart other granted users already open | Same plant; owners / other datasource_access principals execute on refresh | Integrity + recon (other people run the plant) | Inferred. Lab Gamma already had the grant so self-executed |
| Break dashboards that used the old SQL | Replace physical-table sql with attacker SELECT | Availability | Inferred |
| Negative: SQL Lab execute | POST /api/v1/sqllab/execute/ | n/a | Lab-proven. 403 |
| Negative: Dataset REST overwrite | PUT /api/v1/dataset/{id} with sql | n/a | Lab-proven. 403; sql stayed empty (CVE-2026-23982 sibling) |
Negative: v1 ZIP with metadata.yaml | same import endpoint, ZIP bundle | n/a | Lab-proven. 200; sql stayed empty (CVE-2024-26016 class) |
Closed on this leftover: unauthenticated import; registering a new Database URI (v0 dataset import does not create the connection; missing params.database_name is DatabaseNotFoundError); DML through chart/data (has_mutation()); Gamma charting a dataset they cannot read (403); a newly inserted unique table_name (PVM created, not attached to Gamma). Not RCE. Not Admin. Production warehouse split limits SELECT to that URI user. Lab analytics URI was the same Postgres as metadata, so cross-table including ab_user is inferred, not second-oracled.
I pinned Apache Superset 6.1.0. Admin is OS-equivalent and out of scope. I hunted leftovers against Apache's own role matrix: Gamma, Alpha, sql_lab, embedded guest, Public.
The 6.0.0 wave already closed chart/data sqlExpression (CVE-2026-23980), Dataset REST SQL (CVE-2026-23982), and SQL Lab DO $$ / EXPLAIN ANALYZE (CVE-2026-23984). Import authz for versioned ZIP is CVE-2024-26016. Unversioned v0 is still last in command_versions because "the files are not versioned."
import_ on Dashboard maps to write. Non-ZIP JSON is {filename: bytes} with no metadata.yaml. Dispatcher tries v1, catches IncorrectVersionError, runs v0. decode_dashboards turns __SqlaTable__ into a live model. import_dataset looks up and overrides. export_fields includes sql. Query time treats stored SQL as trusted: get_from_clause after has_mutation().
Wrong turns already recorded: treating v1 ZIP as the leftover (sql stayed empty); Dataset REST PUT (403); SQL Lab as Gamma (403); inflating to any Database object (v0 does not register a URI); a reverse shell. Theatre. The witness is the virtual FROM.
python3 superset-g3-v0-import-sql-plant-Abraxas-Labs.py
or:
cd lab
./run.sh
Target only http://127.0.0.1:19710. Image apache/superset:6.1.0. Compose project superset-g3-v0-import. Seed: Admin, Gamma-only user, physical dataset lab_items on Lab Analytics, Gamma datasource_access. Oracle is the witness string in planted SQL and in chart/data.
SUCCESS superset-g3-v0-import-sql-plant gamma-import-ok plant-sql-ok chart-data-witness-ok sqllab-403 dataset-put-403 v1-zip-no-overwrite SUPERSET-G3-V0-IMPORT-SQL-PLANT-WITNESS
v0 import_dataset must require Dataset write and raise_for_access before override copies sql. Drop unversioned v0 from command_versions, or refuse imports that lack metadata.yaml. Keep treating stored virtual SQL as a Dataset-write capability at query time.