VYPR
Critical severityNVD Advisory· Published Mar 20, 2025· Updated Mar 20, 2025

Jdbc Deserialization in h2oai/h2o-3

CVE-2024-10553

Description

A vulnerability in the h2oai/h2o-3 REST API versions 3.46.0.4 allows unauthenticated remote attackers to execute arbitrary code via deserialization of untrusted data. The vulnerability exists in the endpoints POST /99/ImportSQLTable and POST /3/SaveToHiveTable, where user-controlled JDBC URLs are passed to DriverManager.getConnection, leading to deserialization if a MySQL or PostgreSQL driver is available in the classpath. This issue is fixed in version 3.47.0.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Unauthenticated deserialization via JDBC URLs in H2O-3 REST API allows remote code execution before version 3.47.0.

Overview

CVE-2024-10553 is a critical vulnerability in the h2oai/h2o-3 REST API, specifically in endpoints POST /99/ImportSQLTable and POST /3/SaveToHiveTable. The root cause is that user-controlled JDBC URLs are passed directly to DriverManager.getConnection without validation, enabling deserialization of untrusted data when a MySQL or PostgreSQL driver is available in the classpath [1][2].

Exploitation

An unauthenticated remote attacker can supply a malicious JDBC URL containing parameters such as autoDeserialize, queryInterceptors, or allowLoadLocalInfile to trigger deserialization or other dangerous behaviors in the MySQL or PostgreSQL JDBC drivers. The attacker does not need any authentication or prior access, as these endpoints are exposed without user verification [2][4]. The classpath must include the MySQL or PostgreSQL JDBC driver for certain attack vectors, but many H2O-3 deployments include these drivers by default.

Impact

Successful exploitation allows arbitrary code execution on the server running the H2O-3 REST API. This can lead to full compromise of the machine, including data theft, service disruption, and lateral movement within the network [2][4]. The vulnerability is remotely exploitable without authentication, making it particularly dangerous for internet-facing deployments.

Mitigation

H2O-3 version 3.47.0 fixes the issue by adding JDBC URL parameter validation. The commit introduces a list of disallowed parameters (including autoDeserialize, queryInterceptors, and others) and validates the URL before connecting [3]. Users should upgrade to version 3.47.0 or later. If upgrade is not possible, network access to the vulnerable endpoints should be restricted, and the presence of MySQL/PostgreSQL JDBC drivers in the classpath should be reviewed.

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
h2oPyPI
< 3.46.0.63.46.0.6
ai.h2o:h2o-coreMaven
< 3.46.0.63.46.0.6

Affected products

4

Patches

1
ac1d642b4d86

GH-16425 Add JDBC parameter validation [nocheck] (#16432)

https://github.com/h2oai/h2o-3krasinskiOct 27, 2024via ghsa
2 files changed · +65 0
  • h2o-core/src/main/java/water/jdbc/SQLManager.java+45 0 modified
    @@ -5,10 +5,20 @@
     import water.parser.ParseDataset;
     import water.util.Log;
     
    +import java.io.UnsupportedEncodingException;
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URLDecoder;
     import java.sql.*;
    +import java.util.Arrays;
    +import java.util.List;
     import java.util.Objects;
     import java.util.concurrent.ArrayBlockingQueue;
     import java.util.concurrent.atomic.AtomicLong;
    +import java.util.regex.Matcher;
    +import java.util.regex.Pattern;
    +import java.util.stream.Collectors;
    +import java.util.stream.Stream;
     
     public class SQLManager {
     
    @@ -30,6 +40,15 @@ public class SQLManager {
     
       private static final String TMP_TABLE_ENABLED = H2O.OptArgs.SYSTEM_PROP_PREFIX + "sql.tmp_table.enabled";
     
    +  private static final String DISALLOWED_JDBC_PARAMETERS_PARAM = H2O.OptArgs.SYSTEM_PROP_PREFIX + "sql.jdbc.disallowed.parameters";
    +
    +  private static final Pattern JDBC_PARAMETERS_REGEX_PATTERN = Pattern.compile("(?i)[?;&]([a-z]+)=");
    +
    +  private static final List<String> DEFAULT_JDBC_DISALLOWED_PARAMETERS = Stream.of(
    +          "autoDeserialize", "queryInterceptors", "allowLoadLocalInfile", "allowMultiQueries", //mysql 
    +          "allowLoadLocalInfileInPath", "allowUrlInLocalInfile", "allowPublicKeyRetrieval", //mysql 
    +          "init", "script", "shutdown" //h2
    +  ).map(String::toLowerCase).collect(Collectors.toList());
       private static AtomicLong NEXT_TABLE_NUM = new AtomicLong(0);
       
       static Key<Frame> nextTableKey(String prefix, String postfix) {
    @@ -58,6 +77,7 @@ public static Job<Frame> importSqlTable(
           final String username, final String password, final String columns,
           final Boolean useTempTable, final String tempTableName,
           final SqlFetchMode fetchMode, final Integer numChunksHint) {
    +    validateJdbcUrl(connection_url);
     
         final Key<Frame> destination_key = nextTableKey(table, "sql_to_hex");
         final Job<Frame> j = new Job<>(destination_key, Frame.class.getName(), "Import SQL Table");
    @@ -533,6 +553,7 @@ private static int estimateConcurrentConnections(final int cloudSize, final shor
        * @throws SQLException if a database access error occurs or the url is
        */
       public static Connection getConnectionSafe(String url, String username, String password) throws SQLException {
    +    validateJdbcUrl(url);
         initializeDatabaseDriver(getDatabaseType(url));
         try {
           return DriverManager.getConnection(url, username, password);
    @@ -588,6 +609,30 @@ static void initializeDatabaseDriver(String databaseType) {
         }
       }
     
    +  public static void validateJdbcUrl(String jdbcUrl) throws IllegalArgumentException {
    +    if (jdbcUrl == null || jdbcUrl.trim().isEmpty()) {
    +      throw new IllegalArgumentException("JDBC URL is null or empty");
    +    }
    +
    +    if (!jdbcUrl.toLowerCase().startsWith("jdbc:")) {
    +      throw new IllegalArgumentException("JDBC URL must start with 'jdbc:'");
    +    }
    +
    +    Matcher matcher = JDBC_PARAMETERS_REGEX_PATTERN.matcher(jdbcUrl);
    +    String property = System.getProperty(DISALLOWED_JDBC_PARAMETERS_PARAM);
    +    List<String> disallowedParameters = property == null ?
    +            DEFAULT_JDBC_DISALLOWED_PARAMETERS :
    +            Arrays.stream(property.split(",")).map(String::toLowerCase).collect(Collectors.toList());
    +
    +    while (matcher.find()) {
    +      String key = matcher.group(1);
    +      if (disallowedParameters.contains(key.toLowerCase())) {
    +        throw new IllegalArgumentException("Potentially dangerous JDBC parameter found: " + key +
    +                ". That behavior can be altered by setting " + DISALLOWED_JDBC_PARAMETERS_PARAM + " env variable to another comma separated list.");
    +      }
    +    }
    +  }
    +
       static class SqlTableToH2OFrameStreaming {
         final String _table, _columns, _databaseType;
         final int _numCol;
    
  • h2o-core/src/test/java/water/jdbc/SQLManagerTest.java+20 0 modified
    @@ -145,4 +145,24 @@ public void testBuildSelectChunkSql() {
         Assert.assertEquals("SELECT * FROM mytable LIMIT 1310 OFFSET 0",
                 SQLManager.buildSelectChunkSql("", "mytable", 0, 1310, "*", null));
       }
    +
    +  @Test
    +  public void testValidateJdbcConnectionStringH2() {
    +    exception.expect(IllegalArgumentException.class);
    +    exception.expectMessage("Potentially dangerous JDBC parameter found: init");
    +
    +    String h2MaliciousJdbc = "jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE ALIAS RBT AS '@groovy.transform.ASTTest(value={ assert java.lang.Runtime.getRuntime().exec(\"reboot\")" + "})" + "def rbt" + "'";
    +
    +    SQLManager.validateJdbcUrl(h2MaliciousJdbc);
    +  }
    +
    +  @Test
    +  public void testValidateJdbcConnectionStringMysql() {
    +    exception.expect(IllegalArgumentException.class);
    +    exception.expectMessage("Potentially dangerous JDBC parameter found: autoDeserialize");
    +    
    +    String mysqlMaliciousJdbc = "jdbc:mysql://domain:123/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=abcd";
    +
    +    SQLManager.validateJdbcUrl(mysqlMaliciousJdbc);
    +  }
     }
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.