This is an automated email from the ASF dual-hosted git repository. liyang-kylin pushed a commit to branch kylin5 in repository https://gitbox.apache.org/repos/asf/kylin.git
commit ee92c882685503ffea75df5cad98bd2a5e9f4e7c Author: Li Yang <[email protected]> AuthorDate: Fri Oct 2 23:32:38 2026 +0800 KYLIN-6114 reject parenthesized JDBC URL parameters --- .../org/apache/kylin/common/KylinConfigBase.java | 22 ++++++++++++ .../sdk/datasource/framework/utils/JdbcUtils.java | 4 ++- .../DefaultJdbcSourceConnectionValidator.java | 40 +++++++++++++++++++++- .../security/JdbcSourceValidationSettings.java | 3 ++ .../datasource/framework/utils/JdbcUtilsTest.java | 12 +++++++ 5 files changed, 79 insertions(+), 2 deletions(-) diff --git a/src/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java b/src/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java index 8097e6fc27..5f8c49e480 100644 --- a/src/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java +++ b/src/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java @@ -4602,6 +4602,28 @@ public abstract class KylinConfigBase implements Serializable { return Sets.newHashSet(config.split(",")); } + public Set<String> getSourceJdbcWhiteListParenthesisParamKeysByScheme(String scheme) { + Set<String> whiteListSchemes = getSourceJdbcWhiteListSchemes(); + if (!whiteListSchemes.contains(scheme)) { + return Collections.emptySet(); + } + String defaultValue; + switch (scheme) { + case "mysql": + defaultValue = "host,port,address"; + break; + default: + defaultValue = ""; + break; + } + String config = StringUtils.deleteWhitespace(getOptional(String.format(Locale.ROOT, + "kylin.source.jdbc.white-list.%s.parenthesis-param-keys", scheme), defaultValue)); + if (StringUtils.isBlank(config)) { + return Collections.emptySet(); + } + return Sets.newHashSet(config.split(",")); + } + private String getDefaultSourceJdbcDrivers(String scheme) { switch (scheme) { case "h2": diff --git a/src/datasource-sdk/src/main/java/org/apache/kylin/sdk/datasource/framework/utils/JdbcUtils.java b/src/datasource-sdk/src/main/java/org/apache/kylin/sdk/datasource/framework/utils/JdbcUtils.java index 68e8a8eb7a..0028593142 100644 --- a/src/datasource-sdk/src/main/java/org/apache/kylin/sdk/datasource/framework/utils/JdbcUtils.java +++ b/src/datasource-sdk/src/main/java/org/apache/kylin/sdk/datasource/framework/utils/JdbcUtils.java @@ -82,7 +82,9 @@ public class JdbcUtils { JdbcSourceValidationSettings settings = JdbcSourceValidationSettings.builder() .validUrlParamKeys(config.getSourceJdbcWhiteListUrlParamKeysByScheme(scheme)) - .validSemicolonParamKeys(config.getSourceJdbcWhiteListSemicolonParamKeysByScheme(scheme)).build(); + .validSemicolonParamKeys(config.getSourceJdbcWhiteListSemicolonParamKeysByScheme(scheme)) + .validParenthesisParamKeys(config.getSourceJdbcWhiteListParenthesisParamKeysByScheme(scheme)) + .build(); JdbcSourceConnectionValidator validator = (JdbcSourceConnectionValidator) ClassUtil .newInstance(config.getSourceJdbcWhiteListValidatorClassByScheme(scheme)); diff --git a/src/datasource-sdk/src/main/java/org/apache/kylin/sdk/datasource/security/DefaultJdbcSourceConnectionValidator.java b/src/datasource-sdk/src/main/java/org/apache/kylin/sdk/datasource/security/DefaultJdbcSourceConnectionValidator.java index ca0f1819db..c3e01602c6 100644 --- a/src/datasource-sdk/src/main/java/org/apache/kylin/sdk/datasource/security/DefaultJdbcSourceConnectionValidator.java +++ b/src/datasource-sdk/src/main/java/org/apache/kylin/sdk/datasource/security/DefaultJdbcSourceConnectionValidator.java @@ -52,8 +52,10 @@ public class DefaultJdbcSourceConnectionValidator extends AbstractJdbcSourceConn Set<String> queryKeys = parseKeys(query, '&'); Set<String> semicolonKeys = parseSemicolonKeys(urlWithoutQuery); + Set<String> parenthesisKeys = parseParenthesisKeys(urlWithoutQuery); return settings.getValidUrlParamKeys().containsAll(queryKeys) - && settings.getValidSemicolonParamKeys().containsAll(semicolonKeys); + && settings.getValidSemicolonParamKeys().containsAll(semicolonKeys) + && settings.getValidParenthesisParamKeys().containsAll(parenthesisKeys); } catch (IllegalArgumentException e) { return false; } @@ -71,6 +73,42 @@ public class DefaultJdbcSourceConnectionValidator extends AbstractJdbcSourceConn return parseKeys(semicolonContent, ';'); } + private Set<String> parseParenthesisKeys(String urlWithoutQuery) { + if (urlWithoutQuery.indexOf('(') < 0) { + if (urlWithoutQuery.indexOf(')') >= 0) { + throw new IllegalArgumentException("unexpected closing parenthesis"); + } + return Collections.emptySet(); + } + + Set<String> keys = new LinkedHashSet<>(); + int index = 0; + while (index < urlWithoutQuery.length()) { + int groupStart = urlWithoutQuery.indexOf('(', index); + if (groupStart < 0) { + break; + } + int groupEnd = urlWithoutQuery.indexOf(')', groupStart + 1); + if (groupEnd < 0) { + throw new IllegalArgumentException("unbalanced parenthesis"); + } + String group = urlWithoutQuery.substring(groupStart + 1, groupEnd); + if (group.indexOf('(') >= 0 || StringUtils.isBlank(group)) { + throw new IllegalArgumentException("invalid parenthesis content"); + } + for (String key : parseKeys(group, ',')) { + if (!keys.add(key)) { + throw new IllegalArgumentException("duplicate parenthesis parameter"); + } + } + index = groupEnd + 1; + } + if (urlWithoutQuery.indexOf(')', index) >= 0) { + throw new IllegalArgumentException("unexpected closing parenthesis"); + } + return keys; + } + private Set<String> parseKeys(String content, char separator) { if (StringUtils.isBlank(content)) { return Collections.emptySet(); diff --git a/src/datasource-sdk/src/main/java/org/apache/kylin/sdk/datasource/security/JdbcSourceValidationSettings.java b/src/datasource-sdk/src/main/java/org/apache/kylin/sdk/datasource/security/JdbcSourceValidationSettings.java index 3eaad3a6d5..5c222f5710 100644 --- a/src/datasource-sdk/src/main/java/org/apache/kylin/sdk/datasource/security/JdbcSourceValidationSettings.java +++ b/src/datasource-sdk/src/main/java/org/apache/kylin/sdk/datasource/security/JdbcSourceValidationSettings.java @@ -35,4 +35,7 @@ public final class JdbcSourceValidationSettings { @Builder.Default private Set<String> validSemicolonParamKeys = Collections.emptySet(); + + @Builder.Default + private Set<String> validParenthesisParamKeys = Collections.emptySet(); } diff --git a/src/datasource-sdk/src/test/java/org/apache/kylin/sdk/datasource/framework/utils/JdbcUtilsTest.java b/src/datasource-sdk/src/test/java/org/apache/kylin/sdk/datasource/framework/utils/JdbcUtilsTest.java index 989d775a10..034194cf4a 100644 --- a/src/datasource-sdk/src/test/java/org/apache/kylin/sdk/datasource/framework/utils/JdbcUtilsTest.java +++ b/src/datasource-sdk/src/test/java/org/apache/kylin/sdk/datasource/framework/utils/JdbcUtilsTest.java @@ -83,6 +83,18 @@ public class JdbcUtilsTest extends NLocalFileMetadataTestCase { "jdbc:mysql://localhost:3306/db;allowLoadLocalInfile=true", "com.mysql.cj.jdbc.Driver")); } + @Test + public void testValidateUrlByWhiteList_parenthesisSettings() { + String injected = "jdbc:mysql://address=(host=127.0.0.1)(port=61603)(user=fileread_a.txt)" + + "(allowLoadLocalInfile=true)(useSSL=false)(maxAllowedPacket=65535)/"; + assertFalse(JdbcUtils.validateUrlByWhiteList(injected, "com.mysql.cj.jdbc.Driver")); + + assertTrue(JdbcUtils.validateUrlByWhiteList("jdbc:mysql://address=(host=127.0.0.1)(port=3306)/db", + "com.mysql.cj.jdbc.Driver")); + + assertFalse(JdbcUtils.validateUrlByWhiteList("jdbc:h2:mem:db;INIT=(x=y)", "org.h2.Driver")); + } + public static class MockJdbcSourceConnectionValidator extends AbstractJdbcSourceConnectionValidator { @Override public boolean isValid() {
