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() {

Reply via email to