fix(client-adapter/phoenix): 修复 Phoenix ETL params 参数 SQL 注入漏洞

将 SYS_TIME 默认条件和 etlCondition 中的用户输入改为 PreparedStatement
占位符绑定,避免直接拼接 SQL 导致注入。
This commit is contained in:
jianghang.loujh
2026-07-15 14:59:08 +08:00
parent 08d736b33f
commit e20e442446

View File

@@ -172,11 +172,12 @@ public class PhoenixEtlService {
"SELECT * FROM " + dbMapping.getDatabase() + "." + dbMapping.getTable()); "SELECT * FROM " + dbMapping.getDatabase() + "." + dbMapping.getTable());
// 拼接条件 // 拼接条件
appendCondition(params, dbMapping, srcDS, sql); List<Object> values = new ArrayList<>();
appendCondition(params, dbMapping, srcDS, sql, values);
// 获取总数 // 获取总数
String countSql = "SELECT COUNT(1) FROM ( " + sql + ") _CNT "; String countSql = "SELECT COUNT(1) FROM ( " + sql + ") _CNT ";
long cnt = (Long) Util.sqlRS(srcDS, countSql, rs -> { long cnt = (Long) Util.sqlRS(srcDS, countSql, values, rs -> {
Long count = null; Long count = null;
try { try {
if (rs.next()) { if (rs.next()) {
@@ -206,14 +207,14 @@ public class PhoenixEtlService {
sqlFinal = sql + " LIMIT " + offset + "," + cnt; sqlFinal = sql + " LIMIT " + offset + "," + cnt;
} }
executor executor
.execute(() -> executeSqlImport(srcDS, targetDSConnection, sqlFinal, dbMapping, successCount, errMsg, debug)); .execute(() -> executeSqlImport(srcDS, targetDSConnection, sqlFinal, values, dbMapping, successCount, errMsg, debug));
} }
executor.shutdown(); executor.shutdown();
//noinspection StatementWithEmptyBody //noinspection StatementWithEmptyBody
while (!executor.awaitTermination(3, TimeUnit.SECONDS)) ; while (!executor.awaitTermination(3, TimeUnit.SECONDS)) ;
} else { } else {
executeSqlImport(srcDS, targetDSConnection, sql.toString(), dbMapping, successCount, errMsg, debug); executeSqlImport(srcDS, targetDSConnection, sql.toString(), values, dbMapping, successCount, errMsg, debug);
} }
logger.info( logger.info(
@@ -235,7 +236,7 @@ public class PhoenixEtlService {
} }
private static void appendCondition(List<String> params, DbMapping dbMapping, DataSource ds, private static void appendCondition(List<String> params, DbMapping dbMapping, DataSource ds,
StringBuilder sql) { StringBuilder sql, List<Object> values) {
if (params != null && params.size() == 1 && dbMapping.getEtlCondition() == null) { if (params != null && params.size() == 1 && dbMapping.getEtlCondition() == null) {
AtomicBoolean stExists = new AtomicBoolean(false); AtomicBoolean stExists = new AtomicBoolean(false);
// 验证是否有SYS_TIME字段 // 验证是否有SYS_TIME字段
@@ -256,13 +257,27 @@ public class PhoenixEtlService {
return null; return null;
}); });
if (stExists.get()) { if (stExists.get()) {
sql.append(" WHERE SYS_TIME >= '").append(params.get(0)).append("' "); sql.append(" WHERE SYS_TIME >= ? ");
values.add(params.get(0));
} }
} else if (dbMapping.getEtlCondition() != null && params != null && params.size() > 0) { } else if (dbMapping.getEtlCondition() != null && params != null && params.size() > 0) {
String etlCondition = dbMapping.getEtlCondition(); String etlCondition = dbMapping.getEtlCondition();
int size = params.size(); int size = params.size();
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
etlCondition = etlCondition.replace("{" + i + "}", params.get(i)); String placeholder = "{" + i + "}";
int idx = etlCondition.indexOf(placeholder);
if (idx >= 0) {
int start = idx;
int end = idx + placeholder.length();
// 兼容配置中 '{i}' 这种被单引号包裹的写法,避免生成 '?' 语法错误
if (start > 0 && etlCondition.charAt(start - 1) == '\''
&& end < etlCondition.length() && etlCondition.charAt(end) == '\'') {
start--;
end++;
}
etlCondition = etlCondition.substring(0, start) + "?" + etlCondition.substring(end);
values.add(params.get(i));
}
} }
sql.append(" ").append(etlCondition); sql.append(" ").append(etlCondition);
@@ -272,8 +287,8 @@ public class PhoenixEtlService {
/** /**
* 执行导入 * 执行导入
*/ */
private static boolean executeSqlImport(DataSource srcDS, Connection targetDSConnection, String sql, DbMapping dbMapping, private static boolean executeSqlImport(DataSource srcDS, Connection targetDSConnection, String sql, List<Object> values,
AtomicLong successCount, List<String> errMsg, boolean debug) { DbMapping dbMapping, AtomicLong successCount, List<String> errMsg, boolean debug) {
try { try {
Map<String, String> columnsMap = new LinkedHashMap<>(); Map<String, String> columnsMap = new LinkedHashMap<>();
Map<String, Integer> columnType = new LinkedHashMap<>(); Map<String, Integer> columnType = new LinkedHashMap<>();
@@ -300,7 +315,7 @@ public class PhoenixEtlService {
return false; return false;
} }
}); });
Util.sqlRS(srcDS, sql, rs -> { Util.sqlRS(srcDS, sql, values, rs -> {
int idx = 1; int idx = 1;
try { try {
@@ -337,13 +352,13 @@ public class PhoenixEtlService {
pstmt.clearParameters(); pstmt.clearParameters();
// 删除数据 // 删除数据
Map<String, Object> values = new LinkedHashMap<>(); Map<String, Object> deleteValues = new LinkedHashMap<>();
StringBuilder deleteSql = new StringBuilder( StringBuilder deleteSql = new StringBuilder(
"DELETE FROM " + SyncUtil.getDbTableName(dbMapping) + " WHERE "); "DELETE FROM " + SyncUtil.getDbTableName(dbMapping) + " WHERE ");
appendCondition(dbMapping, deleteSql, values, rs); appendCondition(dbMapping, deleteSql, deleteValues, rs);
try (PreparedStatement pstmt2 = connTarget.prepareStatement(deleteSql.toString())) { try (PreparedStatement pstmt2 = connTarget.prepareStatement(deleteSql.toString())) {
int k = 1; int k = 1;
for (Object val : values.values()) { for (Object val : deleteValues.values()) {
pstmt2.setObject(k++, val); pstmt2.setObject(k++, val);
} }
pstmt2.execute(); pstmt2.execute();