Skip to content

Commit 44c38c4

Browse files
aiceflowerclaude
andauthored
#AI COMMIT# Fix JDBC URL encoding bypass and unsafe deserialization vulnerabilities (#5438)
1. SecurityUtils.checkParams: Replace single URL decode with while-loop decode (consistent with checkJdbcConnParams) to prevent double-encoding bypass that allows attackers to smuggle sensitive params like allowLoadLocalInfile past the blacklist. 2. SqlConnection (4 files): Replace DriverManager.getConnection(url,user,pwd) with getConnection(baseUrl, Properties). Security params are set first via SecurityUtils.getMysqlSecurityParams() and cannot be overridden by user-supplied extra params, providing defense-in-depth against URL parameter injection. 3. CryptoUtils.string2Object: Add resolveClass whitelist allowing only java.lang.String, blocking all gadget chain deserialization attacks (CWE-502) while maintaining backward compatibility since passwords are stored as String objects. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 3c55658 commit 44c38c4

6 files changed

Lines changed: 76 additions & 43 deletions

File tree

‎linkis-commons/linkis-common/src/main/java/org/apache/linkis/common/utils/SecurityUtils.java‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -272,12 +272,18 @@ private static void checkParams(Map<String, Object> paramsMap) {
272272
return;
273273
}
274274

275-
// deal with url encode
275+
// deal with url encode - loop until stable to prevent double-encoding bypass
276276
String paramUrl = parseParamsMapToMysqlParamUrl(paramsMap);
277277
try {
278-
paramUrl = URLDecoder.decode(paramUrl, "UTF-8");
278+
while (paramUrl.contains("%")) {
279+
String decodedParamUrl = URLDecoder.decode(paramUrl, "UTF-8");
280+
if (decodedParamUrl.equals(paramUrl)) {
281+
break;
282+
}
283+
paramUrl = decodedParamUrl;
284+
}
279285
} catch (UnsupportedEncodingException e) {
280-
throw new LinkisSecurityException(35000, "mysql connection cul decode error: " + e);
286+
throw new LinkisSecurityException(35000, "mysql connection url decode error: " + e);
281287
}
282288

283289
Map<String, Object> newParamsMap = parseMysqlUrlParamsToMap(paramUrl);

‎linkis-public-enhancements/linkis-datasource/linkis-datasource-manager/service/jdbc/src/main/java/org/apache/linkis/metadata/query/service/mysql/SqlConnection.java‎

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import java.io.IOException;
2727
import java.sql.*;
2828
import java.util.*;
29-
import java.util.stream.Collectors;
3029

3130
import org.slf4j.Logger;
3231
import org.slf4j.LoggerFactory;
@@ -214,19 +213,23 @@ public void close() throws IOException {
214213
*/
215214
private Connection getDBConnection(ConnectMessage connectMessage, String database)
216215
throws ClassNotFoundException, SQLException {
217-
String extraParamString =
218-
connectMessage.extraParams.entrySet().stream()
219-
.map(e -> String.join("=", e.getKey(), String.valueOf(e.getValue())))
220-
.collect(Collectors.joining("&"));
221216
Class.forName(SQL_DRIVER_CLASS.getValue());
222-
String url =
217+
String baseUrl =
223218
String.format(
224219
SQL_CONNECT_URL.getValue(), connectMessage.host, connectMessage.port, database);
225-
if (!connectMessage.extraParams.isEmpty()) {
226-
url += "?" + extraParamString;
220+
221+
// Use Properties-based connection to enforce security params override.
222+
// Per MySQL Connector/J docs, Properties override URL query params on conflict.
223+
Properties props = SecurityUtils.getMysqlSecurityParams();
224+
props.setProperty("user", connectMessage.username);
225+
props.setProperty("password", AESUtils.isDecryptByConf(connectMessage.password));
226+
for (Map.Entry<String, Object> entry : connectMessage.extraParams.entrySet()) {
227+
if (!props.containsKey(entry.getKey())) {
228+
props.setProperty(entry.getKey(), String.valueOf(entry.getValue()));
229+
}
227230
}
228-
return DriverManager.getConnection(
229-
url, connectMessage.username, AESUtils.isDecryptByConf(connectMessage.password));
231+
LOG.info("jdbc connection url: {}", baseUrl);
232+
return DriverManager.getConnection(baseUrl, props);
230233
}
231234

232235
/** Connect message */

‎linkis-public-enhancements/linkis-datasource/linkis-datasource-manager/service/jdbc/src/main/java/org/apache/linkis/metadata/query/service/starrocks/SqlConnection.java‎

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import java.io.IOException;
2727
import java.sql.*;
2828
import java.util.*;
29-
import java.util.stream.Collectors;
3029

3130
import org.slf4j.Logger;
3231
import org.slf4j.LoggerFactory;
@@ -206,19 +205,23 @@ public void close() throws IOException {
206205
*/
207206
private Connection getDBConnection(ConnectMessage connectMessage, String database)
208207
throws ClassNotFoundException, SQLException {
209-
String extraParamString =
210-
connectMessage.extraParams.entrySet().stream()
211-
.map(e -> String.join("=", e.getKey(), String.valueOf(e.getValue())))
212-
.collect(Collectors.joining("&"));
213208
Class.forName(SQL_DRIVER_CLASS.getValue());
214-
String url =
209+
String baseUrl =
215210
String.format(
216211
SQL_CONNECT_URL.getValue(), connectMessage.host, connectMessage.port, database);
217-
if (!connectMessage.extraParams.isEmpty()) {
218-
url += "?" + extraParamString;
212+
213+
// Use Properties-based connection to enforce security params override.
214+
// Per MySQL Connector/J docs, Properties override URL query params on conflict.
215+
Properties props = SecurityUtils.getMysqlSecurityParams();
216+
props.setProperty("user", connectMessage.username);
217+
props.setProperty("password", AESUtils.isDecryptByConf(connectMessage.password));
218+
for (Map.Entry<String, Object> entry : connectMessage.extraParams.entrySet()) {
219+
if (!props.containsKey(entry.getKey())) {
220+
props.setProperty(entry.getKey(), String.valueOf(entry.getValue()));
221+
}
219222
}
220-
return DriverManager.getConnection(
221-
url, connectMessage.username, AESUtils.isDecryptByConf(connectMessage.password));
223+
LOG.info("starrocks jdbc connection url: {}", baseUrl);
224+
return DriverManager.getConnection(baseUrl, props);
222225
}
223226

224227
/** Connect message */

‎linkis-public-enhancements/linkis-datasource/linkis-metadata-query/service/jdbc/src/main/java/org/apache/linkis/metadata/query/service/mysql/SqlConnection.java‎

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -193,23 +193,27 @@ public void close() throws IOException {
193193
*/
194194
private Connection getDBConnection(ConnectMessage connectMessage, String database)
195195
throws ClassNotFoundException, SQLException {
196-
String extraParamString =
197-
connectMessage.extraParams.entrySet().stream()
198-
.map(e -> String.join("=", e.getKey(), String.valueOf(e.getValue())))
199-
.collect(Collectors.joining("&"));
200196
Class.forName(SQL_DRIVER_CLASS.getValue());
201-
String url =
197+
String baseUrl =
202198
String.format(
203199
SQL_CONNECT_URL.getValue(), connectMessage.host, connectMessage.port, database);
204200
// deal with empty database
205201
if (StringUtils.isBlank(database)) {
206-
url = url.substring(0, url.length() - 1);
202+
baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
207203
}
208-
if (!connectMessage.extraParams.isEmpty()) {
209-
url += "?" + extraParamString;
204+
205+
// Use Properties-based connection to enforce security params override.
206+
// Per MySQL Connector/J docs, Properties override URL query params on conflict.
207+
Properties props = SecurityUtils.getMysqlSecurityParams();
208+
props.setProperty("user", connectMessage.username);
209+
props.setProperty("password", connectMessage.password);
210+
for (Map.Entry<String, Object> entry : connectMessage.extraParams.entrySet()) {
211+
if (!props.containsKey(entry.getKey())) {
212+
props.setProperty(entry.getKey(), String.valueOf(entry.getValue()));
213+
}
210214
}
211-
LOG.info("jdbc connection url: {}", url);
212-
return DriverManager.getConnection(url, connectMessage.username, connectMessage.password);
215+
LOG.info("jdbc connection url: {}", baseUrl);
216+
return DriverManager.getConnection(baseUrl, props);
213217
}
214218

215219
/** Connect message */

‎linkis-public-enhancements/linkis-datasource/linkis-metadata-query/service/jdbc/src/main/java/org/apache/linkis/metadata/query/service/starrocks/SqlConnection.java‎

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -206,19 +206,23 @@ public void close() throws IOException {
206206
*/
207207
private Connection getDBConnection(ConnectMessage connectMessage, String database)
208208
throws ClassNotFoundException, SQLException {
209-
String extraParamString =
210-
connectMessage.extraParams.entrySet().stream()
211-
.map(e -> String.join("=", e.getKey(), String.valueOf(e.getValue())))
212-
.collect(Collectors.joining("&"));
213209
Class.forName(SQL_DRIVER_CLASS.getValue());
214-
String url =
210+
String baseUrl =
215211
String.format(
216212
SQL_CONNECT_URL.getValue(), connectMessage.host, connectMessage.port, database);
217-
if (!connectMessage.extraParams.isEmpty()) {
218-
url += "?" + extraParamString;
213+
214+
// Use Properties-based connection to enforce security params override.
215+
// Per MySQL Connector/J docs, Properties override URL query params on conflict.
216+
Properties props = SecurityUtils.getMysqlSecurityParams();
217+
props.setProperty("user", connectMessage.username);
218+
props.setProperty("password", AESUtils.isDecryptByConf(connectMessage.password));
219+
for (Map.Entry<String, Object> entry : connectMessage.extraParams.entrySet()) {
220+
if (!props.containsKey(entry.getKey())) {
221+
props.setProperty(entry.getKey(), String.valueOf(entry.getValue()));
222+
}
219223
}
220-
return DriverManager.getConnection(
221-
url, connectMessage.username, AESUtils.isDecryptByConf(connectMessage.password));
224+
LOG.info("starrocks jdbc connection url: {}", baseUrl);
225+
return DriverManager.getConnection(baseUrl, props);
222226
}
223227

224228
/** Connect message */

‎linkis-public-enhancements/linkis-pes-common/src/main/java/org/apache/linkis/datasourcemanager/common/util/CryptoUtils.java‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323
import java.io.ByteArrayInputStream;
2424
import java.io.ByteArrayOutputStream;
2525
import java.io.IOException;
26+
import java.io.InvalidClassException;
2627
import java.io.ObjectInputStream;
2728
import java.io.ObjectOutputStream;
29+
import java.io.ObjectStreamClass;
2830
import java.io.Serializable;
2931
import java.security.MessageDigest;
3032

@@ -62,7 +64,18 @@ public static String object2String(Serializable o) {
6264
public static Object string2Object(String str) {
6365
try {
6466
ByteArrayInputStream bis = new ByteArrayInputStream(new Base64().decode(str.getBytes()));
65-
ObjectInputStream ois = new ObjectInputStream(bis);
67+
ObjectInputStream ois =
68+
new ObjectInputStream(bis) {
69+
@Override
70+
protected Class<?> resolveClass(ObjectStreamClass desc)
71+
throws IOException, ClassNotFoundException {
72+
if (!desc.getName().equals("java.lang.String")) {
73+
throw new InvalidClassException(
74+
"Unauthorized deserialization attempt", desc.getName());
75+
}
76+
return super.resolveClass(desc);
77+
}
78+
};
6679
Object o = ois.readObject();
6780
bis.close();
6881
ois.close();

0 commit comments

Comments
 (0)