fix:【漏洞】修复 SQL Injection in DataEase v2 Dataset Export

This commit is contained in:
taojinlong
2026-03-17 16:40:54 +08:00
committed by junjun
parent 62042649c9
commit 52b9977bb9
4 changed files with 148 additions and 39 deletions

View File

@@ -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<DatasetTableFieldDTO> originFields, boolean isCross, Map<Long, DatasourceSchemaDTO> dsMap, List<CalParam> fieldParam, List<CalParam> 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();

View File

@@ -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<ChartExtFilterDTO> fields, List<DatasetTableFieldDTO> originFields, boolean isCross, Map<Long, DatasourceSchemaDTO> dsMap, List<CalParam> fieldParam, List<CalParam> 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;
}
}

View File

@@ -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) + "%'";
}
}

View File

@@ -22,6 +22,14 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class Utils {
private static final List<Pattern> 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");
}
}
}
}