diff --git a/core/core-backend/src/main/java/io/dataease/engine/trans/CustomWhere2Str.java b/core/core-backend/src/main/java/io/dataease/engine/trans/CustomWhere2Str.java index d74af0ab24..bc9a3204da 100644 --- a/core/core-backend/src/main/java/io/dataease/engine/trans/CustomWhere2Str.java +++ b/core/core-backend/src/main/java/io/dataease/engine/trans/CustomWhere2Str.java @@ -2,6 +2,7 @@ package io.dataease.engine.trans; import io.dataease.constant.SQLConstants; import io.dataease.engine.utils.Utils; +import io.dataease.exception.DEException; import io.dataease.extensions.datasource.api.PluginManageApi; import io.dataease.extensions.datasource.constant.SqlPlaceholderConstants; import io.dataease.extensions.datasource.dto.CalParam; @@ -18,12 +19,14 @@ import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import java.util.*; +import java.util.regex.Pattern; import java.util.stream.Collectors; /** * @Author Junjun */ public class CustomWhere2Str { + private static final Pattern NUMBER_PATTERN = Pattern.compile("^[-+]?\\d+(\\.\\d+)?([eE][-+]?\\d+)?$"); public static void customWhere2sqlObj(SQLMeta meta, FilterTreeObj tree, List originFields, boolean isCross, Map dsMap, List fieldParam, List chartParam, PluginManageApi pluginManage) { SQLObj tableObj = meta.getTable(); @@ -171,9 +174,9 @@ public class CustomWhere2Str { || StringUtils.containsIgnoreCase(field.getType(), "NCHAR")) && !isCross && StringUtils.equalsIgnoreCase(dsType, DatasourceConfiguration.DatasourceType.sqlServer.getType())) { - res = "(" + whereName + " IN (" + item.getEnumValue().stream().map(str -> "'" + SQLConstants.MSSQL_N_PREFIX + str + "'").collect(Collectors.joining(",")) + "))"; + res = "(" + whereName + " IN (" + item.getEnumValue().stream().map(CustomWhere2Str::toSqlServerNQuotedValue).collect(Collectors.joining(",")) + "))"; } else { - res = "(" + whereName + " IN ('" + String.join("','", item.getEnumValue()) + "'))"; + res = "(" + whereName + " IN (" + item.getEnumValue().stream().map(CustomWhere2Str::toQuotedValue).collect(Collectors.joining(",")) + "))"; } } } else { @@ -199,18 +202,18 @@ public class CustomWhere2Str { || StringUtils.containsIgnoreCase(field.getType(), "NCHAR")) && !isCross && StringUtils.equalsIgnoreCase(dsType, DatasourceConfiguration.DatasourceType.sqlServer.getType())) { - whereValue = "(" + Arrays.stream(value.split(",")).map(str -> "'" + SQLConstants.MSSQL_N_PREFIX + str + "'").collect(Collectors.joining(",")) + ")"; + whereValue = "(" + Arrays.stream(value.split(",")).map(CustomWhere2Str::toSqlServerNQuotedValue).collect(Collectors.joining(",")) + ")"; } else { - whereValue = "('" + String.join("','", value.split(",")) + "')"; + whereValue = "(" + Arrays.stream(value.split(",")).map(CustomWhere2Str::toQuotedValue).collect(Collectors.joining(",")) + ")"; } } else if (StringUtils.containsIgnoreCase(item.getTerm(), "like")) { if ((StringUtils.containsIgnoreCase(field.getType(), "NVARCHAR") || StringUtils.containsIgnoreCase(field.getType(), "NCHAR")) && !isCross && StringUtils.equalsIgnoreCase(dsType, DatasourceConfiguration.DatasourceType.sqlServer.getType())) { - whereValue = "'" + SQLConstants.MSSQL_N_PREFIX + "%" + value + "%'"; + whereValue = toSqlServerNLikeValue(value); } else { - whereValue = "'%" + value + "%'"; + whereValue = toLikeValue(value); } } else { // 如果是时间字段过滤,当条件是等于和不等于的时候转换成between和not between @@ -249,21 +252,21 @@ public class CustomWhere2Str { value = Utils.transLong2Str(startTime); } } - whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE, value); + whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE, sanitizeSqlLiteral(value)); } } else { if ((StringUtils.containsIgnoreCase(field.getType(), "NVARCHAR") || StringUtils.containsIgnoreCase(field.getType(), "NCHAR")) && !isCross && StringUtils.equalsIgnoreCase(dsType, DatasourceConfiguration.DatasourceType.sqlServer.getType())) { - whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE_CH, value); + whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE_CH, sanitizeSqlLiteral(value)); } else { if (field.getDeType() == 2 || field.getDeType() == 3 || field.getDeType() == 4) { - whereValue = String.format(SQLConstants.WHERE_NUMBER_VALUE, value); + whereValue = String.format(SQLConstants.WHERE_NUMBER_VALUE, sanitizeNumberLiteral(value)); } else { - whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE, value); + whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE, sanitizeSqlLiteral(value)); } } } @@ -277,6 +280,36 @@ public class CustomWhere2Str { return res; } + private static String sanitizeSqlLiteral(String value) { + String normalized = StringUtils.defaultString(value); + Utils.validateSqlInjectionRisk(normalized); + return Utils.transValue(normalized); + } + + private static String toQuotedValue(String value) { + return "'" + sanitizeSqlLiteral(value) + "'"; + } + + private static String toLikeValue(String value) { + return "'%" + sanitizeSqlLiteral(value) + "%'"; + } + + private static String toSqlServerNQuotedValue(String value) { + return "'" + SQLConstants.MSSQL_N_PREFIX + sanitizeSqlLiteral(value) + "'"; + } + + private static String toSqlServerNLikeValue(String value) { + return "'" + SQLConstants.MSSQL_N_PREFIX + "%" + sanitizeSqlLiteral(value) + "%'"; + } + + private static String sanitizeNumberLiteral(String value) { + String normalized = StringUtils.trimToEmpty(value); + if (!NUMBER_PATTERN.matcher(normalized).matches()) { + DEException.throwException("Illegal number filter value"); + } + return normalized; + } + private static String fixValue(FilterTreeItem item) { if (StringUtils.isNotEmpty(item.getFilterTypeTime()) && StringUtils.equalsIgnoreCase(item.getFilterTypeTime(), "dynamicDate")) { DynamicTimeSetting dynamicTimeSetting = item.getDynamicTimeSetting(); diff --git a/core/core-backend/src/main/java/io/dataease/engine/trans/ExtWhere2Str.java b/core/core-backend/src/main/java/io/dataease/engine/trans/ExtWhere2Str.java index 6855b00d21..748684b443 100644 --- a/core/core-backend/src/main/java/io/dataease/engine/trans/ExtWhere2Str.java +++ b/core/core-backend/src/main/java/io/dataease/engine/trans/ExtWhere2Str.java @@ -2,6 +2,7 @@ package io.dataease.engine.trans; import io.dataease.constant.SQLConstants; import io.dataease.engine.utils.Utils; +import io.dataease.exception.DEException; import io.dataease.extensions.datasource.api.PluginManageApi; import io.dataease.extensions.datasource.constant.SqlPlaceholderConstants; import io.dataease.extensions.datasource.dto.CalParam; @@ -18,12 +19,14 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; import java.util.stream.Collectors; /** * @Author Junjun */ public class ExtWhere2Str { + private static final Pattern NUMBER_PATTERN = Pattern.compile("^[-+]?\\d+(\\.\\d+)?([eE][-+]?\\d+)?$"); public static void extWhere2sqlOjb(SQLMeta meta, List fields, List originFields, boolean isCross, Map dsMap, List fieldParam, List chartParam, PluginManageApi pluginManage) { SQLObj tableObj = meta.getTable(); @@ -158,7 +161,8 @@ public class ExtWhere2Str { } else if (StringUtils.containsIgnoreCase(request.getOperator(), "in")) { // 过滤空数据 if (value.contains(SQLConstants.EMPTY_SIGN)) { - whereValue = "('" + StringUtils.join(value, "','") + "', '')" + " or " + whereName + " is null "; + String joined = value.stream().map(ExtWhere2Str::sanitizeSqlLiteral).collect(Collectors.joining("','")); + whereValue = "('" + joined + "', '')" + " or " + whereName + " is null "; } else { // tree的情况需额外处理 if (request.getIsTree()) { @@ -172,21 +176,21 @@ public class ExtWhere2Str { } } if (hasN && !isCross && StringUtils.equalsIgnoreCase(dsType, DatasourceConfiguration.DatasourceType.sqlServer.getType())) { - whereValue = "(" + value.stream().map(str -> "'" + SQLConstants.MSSQL_N_PREFIX + str + "'").collect(Collectors.joining(",")) + ")"; + whereValue = "(" + value.stream().map(ExtWhere2Str::toSqlServerNQuotedValue).collect(Collectors.joining(",")) + ")"; } else { - whereValue = "('" + StringUtils.join(value, "','") + "')"; + whereValue = "(" + value.stream().map(ExtWhere2Str::toQuotedValue).collect(Collectors.joining(",")) + ")"; } } else { if ((StringUtils.containsIgnoreCase(request.getDatasetTableField().getType(), "NVARCHAR") || StringUtils.containsIgnoreCase(request.getDatasetTableField().getType(), "NCHAR")) && !isCross && StringUtils.equalsIgnoreCase(dsType, DatasourceConfiguration.DatasourceType.sqlServer.getType())) { - whereValue = "(" + value.stream().map(str -> "'" + SQLConstants.MSSQL_N_PREFIX + str + "'").collect(Collectors.joining(",")) + ")"; + whereValue = "(" + value.stream().map(ExtWhere2Str::toSqlServerNQuotedValue).collect(Collectors.joining(",")) + ")"; } else { if (request.getDatasetTableField().getDeType() == 2 || request.getDatasetTableField().getDeType() == 3) { - whereValue = "(" + StringUtils.join(value, ",") + ")"; + whereValue = "(" + value.stream().map(ExtWhere2Str::sanitizeNumberLiteral).collect(Collectors.joining(",")) + ")"; } else { - whereValue = "('" + StringUtils.join(value, "','") + "')"; + whereValue = "(" + value.stream().map(ExtWhere2Str::toQuotedValue).collect(Collectors.joining(",")) + ")"; } } } @@ -204,18 +208,18 @@ public class ExtWhere2Str { } } if (hasN && !isCross && StringUtils.equalsIgnoreCase(dsType, DatasourceConfiguration.DatasourceType.sqlServer.getType())) { - whereValue = "'" + SQLConstants.MSSQL_N_PREFIX + "%" + value.get(0) + "%'"; + whereValue = toSqlServerNLikeValue(value.get(0)); } else { - whereValue = "'%" + value.get(0) + "%'"; + whereValue = toLikeValue(value.get(0)); } } else { if ((StringUtils.containsIgnoreCase(request.getDatasetTableField().getType(), "NVARCHAR") || StringUtils.containsIgnoreCase(request.getDatasetTableField().getType(), "NCHAR")) && !isCross && StringUtils.equalsIgnoreCase(dsType, DatasourceConfiguration.DatasourceType.sqlServer.getType())) { - whereValue = "'" + SQLConstants.MSSQL_N_PREFIX + "%" + value.get(0) + "%'"; + whereValue = toSqlServerNLikeValue(value.get(0)); } else { - whereValue = "'%" + value.get(0) + "%'"; + whereValue = toLikeValue(value.get(0)); } } } else if (StringUtils.containsIgnoreCase(request.getOperator(), "between")) { @@ -224,7 +228,7 @@ public class ExtWhere2Str { || request.getDatasetTableField().getDeExtractType() == 3 || request.getDatasetTableField().getDeExtractType() == 4) { if (isCross) { - whereValue = String.format(SQLConstants.WHERE_VALUE_BETWEEN, value.get(0), value.get(1)); + whereValue = String.format(SQLConstants.WHERE_VALUE_BETWEEN, sanitizeNumberLiteral(value.get(0)), sanitizeNumberLiteral(value.get(1))); } else { whereValue = String.format(SQLConstants.WHERE_BETWEEN, Utils.transLong2Str(Long.parseLong(value.get(0))), Utils.transLong2Str(Long.parseLong(value.get(1)))); } @@ -243,9 +247,9 @@ public class ExtWhere2Str { } else if (request.getDatasetTableField().getDeType() == 2 || request.getDatasetTableField().getDeType() == 3 || request.getDatasetTableField().getDeType() == 4) { - whereValue = String.format(SQLConstants.WHERE_VALUE_BETWEEN, value.get(0), value.get(1)); + whereValue = String.format(SQLConstants.WHERE_VALUE_BETWEEN, sanitizeNumberLiteral(value.get(0)), sanitizeNumberLiteral(value.get(1))); } else { - whereValue = String.format(SQLConstants.WHERE_BETWEEN, value.get(0), value.get(1)); + whereValue = String.format(SQLConstants.WHERE_BETWEEN, sanitizeSqlLiteral(value.get(0)), sanitizeSqlLiteral(value.get(1))); } } else { // 过滤空数据 @@ -265,23 +269,23 @@ public class ExtWhere2Str { } } if (hasN && !isCross) { - whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE_CH, value.get(0)); + whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE_CH, sanitizeSqlLiteral(value.get(0))); } else { - whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE, value.get(0)); + whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE, sanitizeSqlLiteral(value.get(0))); } } else { if ((StringUtils.containsIgnoreCase(request.getDatasetTableField().getType(), "NVARCHAR") || StringUtils.containsIgnoreCase(request.getDatasetTableField().getType(), "NCHAR")) && !isCross && StringUtils.equalsIgnoreCase(dsType, DatasourceConfiguration.DatasourceType.sqlServer.getType())) { - whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE_CH, value.get(0)); + whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE_CH, sanitizeSqlLiteral(value.get(0))); } else { if (request.getDatasetTableField().getDeType() == 2 || request.getDatasetTableField().getDeType() == 3 || request.getDatasetTableField().getDeType() == 4) { - whereValue = String.format(SQLConstants.WHERE_NUMBER_VALUE, value.get(0)); + whereValue = String.format(SQLConstants.WHERE_NUMBER_VALUE, sanitizeNumberLiteral(value.get(0))); } else { - whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE, value.get(0)); + whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE, sanitizeSqlLiteral(value.get(0))); } } } @@ -302,11 +306,41 @@ public class ExtWhere2Str { private static String getValue(String term, String value) { switch (term) { case "like": - return "'%" + value + "%'"; + return toLikeValue(value); case "eq": - return "'" + value + "'"; + return toQuotedValue(value); } return null; } + private static String sanitizeSqlLiteral(String value) { + String normalized = StringUtils.defaultString(value); + Utils.validateSqlInjectionRisk(normalized); + return Utils.transValue(normalized); + } + + private static String toQuotedValue(String value) { + return "'" + sanitizeSqlLiteral(value) + "'"; + } + + private static String toLikeValue(String value) { + return "'%" + sanitizeSqlLiteral(value) + "%'"; + } + + private static String toSqlServerNQuotedValue(String value) { + return "'" + SQLConstants.MSSQL_N_PREFIX + sanitizeSqlLiteral(value) + "'"; + } + + private static String toSqlServerNLikeValue(String value) { + return "'" + SQLConstants.MSSQL_N_PREFIX + "%" + sanitizeSqlLiteral(value) + "%'"; + } + + private static String sanitizeNumberLiteral(String value) { + String normalized = StringUtils.trimToEmpty(value); + if (!NUMBER_PATTERN.matcher(normalized).matches()) { + DEException.throwException("Illegal number filter value"); + } + return normalized; + } + } diff --git a/core/core-backend/src/main/java/io/dataease/engine/trans/WhereTree2Str.java b/core/core-backend/src/main/java/io/dataease/engine/trans/WhereTree2Str.java index 451ada7d3e..f8c6a244f9 100644 --- a/core/core-backend/src/main/java/io/dataease/engine/trans/WhereTree2Str.java +++ b/core/core-backend/src/main/java/io/dataease/engine/trans/WhereTree2Str.java @@ -184,9 +184,9 @@ public class WhereTree2Str { || StringUtils.containsIgnoreCase(field.getType(), "NCHAR")) && !isCross && StringUtils.equalsIgnoreCase(dsType, DatasourceConfiguration.DatasourceType.sqlServer.getType())) { - res = "(" + whereName + " IN (" + item.getEnumValue().stream().map(str -> "'" + SQLConstants.MSSQL_N_PREFIX + str + "'").collect(Collectors.joining(",")) + "))"; + res = "(" + whereName + " IN (" + item.getEnumValue().stream().map(WhereTree2Str::toSqlServerNQuotedValue).collect(Collectors.joining(",")) + "))"; } else { - res = "(" + whereName + " IN ('" + String.join("','", item.getEnumValue()) + "'))"; + res = "(" + whereName + " IN (" + item.getEnumValue().stream().map(WhereTree2Str::toQuotedValue).collect(Collectors.joining(",")) + "))"; } } } else { @@ -211,18 +211,18 @@ public class WhereTree2Str { || StringUtils.containsIgnoreCase(field.getType(), "NCHAR")) && !isCross && StringUtils.equalsIgnoreCase(dsType, DatasourceConfiguration.DatasourceType.sqlServer.getType())) { - whereValue = "(" + Arrays.stream(value.split(",")).map(str -> "'" + SQLConstants.MSSQL_N_PREFIX + str + "'").collect(Collectors.joining(",")) + ")"; + whereValue = "(" + Arrays.stream(value.split(",")).map(WhereTree2Str::toSqlServerNQuotedValue).collect(Collectors.joining(",")) + ")"; } else { - whereValue = "('" + String.join("','", value.split(",")) + "')"; + whereValue = "(" + Arrays.stream(value.split(",")).map(WhereTree2Str::toQuotedValue).collect(Collectors.joining(",")) + ")"; } } else if (StringUtils.containsIgnoreCase(item.getTerm(), "like")) { if ((StringUtils.containsIgnoreCase(field.getType(), "NVARCHAR") || StringUtils.containsIgnoreCase(field.getType(), "NCHAR")) && !isCross && StringUtils.equalsIgnoreCase(dsType, DatasourceConfiguration.DatasourceType.sqlServer.getType())) { - whereValue = "'" + SQLConstants.MSSQL_N_PREFIX + "%" + value + "%'"; + whereValue = toSqlServerNLikeValue(value); } else { - whereValue = "'%" + value + "%'"; + whereValue = toLikeValue(value); } } else { // 如果是时间字段过滤,当条件是等于和不等于的时候转换成between和not between @@ -259,16 +259,16 @@ public class WhereTree2Str { value = Utils.transLong2Str(startTime); } } - whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE, value); + whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE, sanitizeSqlLiteral(value)); } } else { if ((StringUtils.containsIgnoreCase(field.getType(), "NVARCHAR") || StringUtils.containsIgnoreCase(field.getType(), "NCHAR")) && !isCross && StringUtils.equalsIgnoreCase(dsType, DatasourceConfiguration.DatasourceType.sqlServer.getType())) { - whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE_CH, value); + whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE_CH, sanitizeSqlLiteral(value)); } else { - whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE, value); + whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE, sanitizeSqlLiteral(value)); } } } @@ -277,4 +277,26 @@ public class WhereTree2Str { } return res; } + + private static String sanitizeSqlLiteral(String value) { + String normalized = StringUtils.defaultString(value); + Utils.validateSqlInjectionRisk(normalized); + return Utils.transValue(normalized); + } + + private static String toQuotedValue(String value) { + return "'" + sanitizeSqlLiteral(value) + "'"; + } + + private static String toLikeValue(String value) { + return "'%" + sanitizeSqlLiteral(value) + "%'"; + } + + private static String toSqlServerNQuotedValue(String value) { + return "'" + SQLConstants.MSSQL_N_PREFIX + sanitizeSqlLiteral(value) + "'"; + } + + private static String toSqlServerNLikeValue(String value) { + return "'" + SQLConstants.MSSQL_N_PREFIX + "%" + sanitizeSqlLiteral(value) + "%'"; + } } diff --git a/core/core-backend/src/main/java/io/dataease/engine/utils/Utils.java b/core/core-backend/src/main/java/io/dataease/engine/utils/Utils.java index 05efcbc477..6fcf99b01c 100644 --- a/core/core-backend/src/main/java/io/dataease/engine/utils/Utils.java +++ b/core/core-backend/src/main/java/io/dataease/engine/utils/Utils.java @@ -22,6 +22,14 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; public class Utils { + private static final List SQL_INJECTION_PATTERNS = Arrays.asList( + Pattern.compile("[\\'\";`]"), + Pattern.compile("--\\s*|#"), + Pattern.compile("\\b(or|and|union|select|insert|delete|update|drop|alter|exec|xp_cmdshell)\\b", Pattern.CASE_INSENSITIVE), + Pattern.compile("\\b\\d+\\s*=\\s*\\d+\\b", Pattern.CASE_INSENSITIVE), + Pattern.compile("\\b1'\\s*=\\s*'1\\b", Pattern.CASE_INSENSITIVE) + ); + public static boolean joinSort(String sort) { return (StringUtils.equalsIgnoreCase(sort, "asc") || StringUtils.equalsIgnoreCase(sort, "desc")); } @@ -573,4 +581,16 @@ public class Utils { public static String transValue(String value) { return value.replace("\\", "\\\\").replace("'", "''"); } + + public static void validateSqlInjectionRisk(String value) { + String normalized = StringUtils.defaultString(value); + if (StringUtils.isEmpty(normalized)) { + return; + } + for (Pattern pattern : SQL_INJECTION_PATTERNS) { + if (pattern.matcher(normalized).find()) { + DEException.throwException("Illegal filter value"); + } + } + } }