From f271cf3420db1fa34db8a9f0caf608d241c18771 Mon Sep 17 00:00:00 2001 From: zhangdaiscott Date: Wed, 26 Aug 2026 10:20:34 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E3=80=90issues/9840=E3=80=91?= =?UTF-8?q?=E4=B8=B4=E6=97=B6=E5=8A=A0=E5=9B=BA=E8=A1=A8=E5=AD=97=E5=85=B8?= =?UTF-8?q?=E6=95=8F=E6=84=9F=E5=AD=97=E6=AE=B5=E8=AE=BF=E9=97=AE=E9=A3=8E?= =?UTF-8?q?=E9=99=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 禁止字典接口查询或通过条件、排序使用敏感字段。 2. 当前限制用户密码、数据源账号密码、OpenAPI密钥及AI模型凭据。 3. 字典条件增加结构化校验,拦截子查询、函数及附加SQL。 4. 增加前端敏感字段访问测试示例。 说明:本次为临时方案,通过敏感表和敏感字段清单进行拦截,暂未调整现有动态SQL架构 --- .../util/DictSqlConditionCheckUtil.java | 253 ++++++++++++++++++ .../common/util/SensitiveTableCheckUtil.java | 83 ++++++ .../jeecg/common/util/SqlInjectionUtil.java | 34 +++ .../service/impl/SysDictServiceImpl.java | 37 ++- .../src/views/demo/jeecg/JeecgComponents.vue | 66 +++++ 5 files changed, 460 insertions(+), 13 deletions(-) create mode 100644 jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/DictSqlConditionCheckUtil.java create mode 100644 jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/SensitiveTableCheckUtil.java diff --git a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/DictSqlConditionCheckUtil.java b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/DictSqlConditionCheckUtil.java new file mode 100644 index 000000000..47374cbbf --- /dev/null +++ b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/DictSqlConditionCheckUtil.java @@ -0,0 +1,253 @@ +package org.jeecg.common.util; + +import net.sf.jsqlparser.expression.BinaryExpression; +import net.sf.jsqlparser.expression.DoubleValue; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.expression.SignedExpression; +import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.expression.operators.conditional.AndExpression; +import net.sf.jsqlparser.expression.operators.conditional.OrExpression; +import net.sf.jsqlparser.expression.operators.relational.Between; +import net.sf.jsqlparser.expression.operators.relational.EqualsTo; +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; +import net.sf.jsqlparser.expression.operators.relational.GreaterThan; +import net.sf.jsqlparser.expression.operators.relational.GreaterThanEquals; +import net.sf.jsqlparser.expression.operators.relational.InExpression; +import net.sf.jsqlparser.expression.operators.relational.IsNullExpression; +import net.sf.jsqlparser.expression.operators.relational.LikeExpression; +import net.sf.jsqlparser.expression.operators.relational.MinorThan; +import net.sf.jsqlparser.expression.operators.relational.MinorThanEquals; +import net.sf.jsqlparser.expression.operators.relational.NotEqualsTo; +import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList; +import net.sf.jsqlparser.parser.CCJSqlParser; +import net.sf.jsqlparser.parser.CCJSqlParserConstants; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * 前端字典条件 SQL 结构校验工具。 + * + * @author scott + * @since 2026-08-25 issues/9840 字典过滤条件 SQL 注入防护 + */ +public final class DictSqlConditionCheckUtil { + + private DictSqlConditionCheckUtil() { + } + + /** + * 校验字典条件语法并返回条件中使用的字段。 + * + * @param value 字典过滤条件 + * @return 条件字段集合 + * @throws IllegalArgumentException 条件包含不支持或不安全的 SQL 结构 + */ + public static Set checkAndGetFields(String value) { + if (value == null || value.trim().isEmpty()) { + return Set.of(); + } + String trimmed = value.trim(); + Set conditionFields = new LinkedHashSet<>(); + try { + if (containsSqlComment(trimmed)) { + throw new IllegalArgumentException("字典过滤条件不允许包含SQL注释"); + } + int orderByIndex = findTopLevelOrderBy(trimmed); + String conditionSql = orderByIndex < 0 ? trimmed : trimmed.substring(0, orderByIndex).trim(); + String orderBySql = orderByIndex < 0 ? null + : trimmed.substring(orderByIndex).replaceFirst("(?i)^order\\s+by\\s+", "").trim(); + if ((!conditionSql.isEmpty() && !validateCondition(conditionSql, conditionFields)) + || (orderBySql != null && !validateOrderBy(orderBySql, conditionFields))) { + throw new IllegalArgumentException("不支持的字典过滤条件"); + } + return conditionFields; + } catch (Exception e) { + throw new IllegalArgumentException("不支持的字典过滤条件", e); + } + } + + private static boolean validateCondition(String value, Set conditionFields) throws Exception { + CCJSqlParser parser = CCJSqlParserUtil.newParser(value); + Expression expression = parser.Expression(); + return parser.getNextToken().kind == CCJSqlParserConstants.EOF + && validateExpression(expression, conditionFields); + } + + private static boolean validateOrderBy(String value, Set conditionFields) throws Exception { + if (value.isEmpty()) { + return false; + } + for (String orderItem : value.split(",")) { + String[] parts = orderItem.trim().split("\\s+"); + if (parts.length == 0 || parts.length > 2 + || (parts.length == 2 && !"ASC".equalsIgnoreCase(parts[1]) && !"DESC".equalsIgnoreCase(parts[1]))) { + return false; + } + CCJSqlParser parser = CCJSqlParserUtil.newParser(parts[0]); + Expression expression = parser.Expression(); + if (parser.getNextToken().kind != CCJSqlParserConstants.EOF + || !addConditionField(expression, conditionFields)) { + return false; + } + } + return true; + } + + /** + * 定位字符串和括号之外的 ORDER BY,避免误判条件值中的普通文本。 + */ + private static int findTopLevelOrderBy(String value) { + char quote = 0; + int depth = 0; + for (int i = 0; i < value.length(); i++) { + char current = value.charAt(i); + if (quote != 0) { + if (current == '\\' && i + 1 < value.length()) { + i++; + } else if (current == quote) { + if (i + 1 < value.length() && value.charAt(i + 1) == quote) { + i++; + } else { + quote = 0; + } + } + continue; + } + if (current == '\'' || current == '"' || current == '`') { + quote = current; + } else if (current == '(') { + depth++; + } else if (current == ')') { + depth--; + } else if (depth == 0 && isKeyword(value, i, "order")) { + int byIndex = i + "order".length(); + if (byIndex < value.length() && Character.isWhitespace(value.charAt(byIndex))) { + while (byIndex < value.length() && Character.isWhitespace(value.charAt(byIndex))) { + byIndex++; + } + if (isKeyword(value, byIndex, "by")) { + return i; + } + } + } + } + return -1; + } + + private static boolean isKeyword(String value, int start, String keyword) { + if (start < 0 || start + keyword.length() > value.length() + || !value.regionMatches(true, start, keyword, 0, keyword.length())) { + return false; + } + boolean leftBoundary = start == 0 || !Character.isJavaIdentifierPart(value.charAt(start - 1)); + int end = start + keyword.length(); + boolean rightBoundary = end == value.length() || !Character.isJavaIdentifierPart(value.charAt(end)); + return leftBoundary && rightBoundary; + } + + /** + * 仅识别字符串和引用标识符之外的 SQL 注释,避免误伤普通文本中的注释符号。 + */ + private static boolean containsSqlComment(String value) { + char quote = 0; + for (int i = 0; i < value.length(); i++) { + char current = value.charAt(i); + if (quote != 0) { + if (current == '\\' && i + 1 < value.length()) { + i++; + } else if (current == quote) { + if (i + 1 < value.length() && value.charAt(i + 1) == quote) { + i++; + } else { + quote = 0; + } + } + continue; + } + if (current == '\'' || current == '"' || current == '`') { + quote = current; + continue; + } + if (i + 1 < value.length() + && ((current == '-' && value.charAt(i + 1) == '-') + || (current == '/' && value.charAt(i + 1) == '*'))) { + return true; + } + } + return false; + } + + private static boolean validateExpression(Expression expression, Set conditionFields) { + if (expression instanceof ParenthesedExpressionList expressionList) { + return expressionList.size() == 1 && validateExpression(expressionList.get(0), conditionFields); + } + if (expression instanceof AndExpression || expression instanceof OrExpression) { + BinaryExpression logicalExpression = (BinaryExpression) expression; + return validateExpression(logicalExpression.getLeftExpression(), conditionFields) + && validateExpression(logicalExpression.getRightExpression(), conditionFields); + } + if (isComparisonExpression(expression)) { + BinaryExpression comparison = (BinaryExpression) expression; + return addConditionField(comparison.getLeftExpression(), conditionFields) + && isLiteral(comparison.getRightExpression()); + } + if (expression instanceof LikeExpression likeExpression) { + return addConditionField(likeExpression.getLeftExpression(), conditionFields) + && likeExpression.getRightExpression() instanceof StringValue + && (likeExpression.getEscape() == null || likeExpression.getEscape() instanceof StringValue); + } + if (expression instanceof InExpression inExpression) { + return addConditionField(inExpression.getLeftExpression(), conditionFields) + && isLiteralList(inExpression.getRightExpression()); + } + if (expression instanceof Between between) { + return addConditionField(between.getLeftExpression(), conditionFields) + && isLiteral(between.getBetweenExpressionStart()) + && isLiteral(between.getBetweenExpressionEnd()); + } + if (expression instanceof IsNullExpression isNullExpression) { + return addConditionField(isNullExpression.getLeftExpression(), conditionFields); + } + return false; + } + + private static boolean isComparisonExpression(Expression expression) { + return expression instanceof EqualsTo + || expression instanceof NotEqualsTo + || expression instanceof GreaterThan + || expression instanceof GreaterThanEquals + || expression instanceof MinorThan + || expression instanceof MinorThanEquals; + } + + private static boolean addConditionField(Expression expression, Set conditionFields) { + if (!(expression instanceof Column column)) { + return false; + } + conditionFields.add(column.getFullyQualifiedName()); + return true; + } + + private static boolean isLiteralList(Expression expression) { + if (!(expression instanceof ExpressionList expressionList) || expressionList.isEmpty()) { + return false; + } + return expressionList.stream().allMatch(DictSqlConditionCheckUtil::isLiteral); + } + + private static boolean isLiteral(Expression expression) { + if (expression instanceof LongValue || expression instanceof DoubleValue || expression instanceof StringValue) { + return true; + } + if (expression instanceof SignedExpression signedExpression) { + char sign = signedExpression.getSign(); + Expression number = signedExpression.getExpression(); + return (sign == '-' || sign == '+') && (number instanceof LongValue || number instanceof DoubleValue); + } + return false; + } +} diff --git a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/SensitiveTableCheckUtil.java b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/SensitiveTableCheckUtil.java new file mode 100644 index 000000000..867fdbc67 --- /dev/null +++ b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/SensitiveTableCheckUtil.java @@ -0,0 +1,83 @@ +package org.jeecg.common.util; + +import lombok.extern.slf4j.Slf4j; +import org.jeecg.common.exception.JeecgSqlInjectionException; + +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +/** + * 系统敏感表字段校验工具。 + * + * @author scott + * @since 2026-08-25 issues/9840 防止通过前端字典条件探测系统敏感信息 + */ +@Slf4j +public final class SensitiveTableCheckUtil { + + private static final String SQL_INJECTION_TIP = "请注意,值可能存在SQL注入风险!--->"; + private static final Map> SENSITIVE_FIELDS = Map.of( + "sys_user", Set.of("password", "salt"), + "sys_data_source", Set.of("db_url", "db_username", "db_password"), + "open_api_auth", Set.of("ak", "sk"), + "airag_model", Set.of("credential") + ); + + private SensitiveTableCheckUtil() { + } + + /** + * 校验字典查询是否访问禁止字段。 + * + * @param table 查询表名 + * @param fields 查询字段 + */ + public static void checkForbiddenFields(String table, String... fields) { + if (oConvertUtils.isEmpty(table) || fields == null || fields.length == 0) { + return; + } + String tableName = getTableName(table); + Set sensitiveFields = SENSITIVE_FIELDS.get(tableName); + if (sensitiveFields == null) { + return; + } + + for (String field : fields) { + if (oConvertUtils.isEmpty(field)) { + continue; + } + for (String fieldItem : field.split(",")) { + checkSensitiveField(tableName, sensitiveFields, fieldItem); + } + } + } + + private static void checkSensitiveField(String tableName, Set sensitiveFields, String field) { + String fieldName = normalizeName(field); + if ("*".equals(fieldName) || sensitiveFields.contains(fieldName)) { + log.error("字典查询不允许使用敏感字段:{}.{}", tableName, fieldName); + throw new JeecgSqlInjectionException(SQL_INJECTION_TIP + tableName + "." + fieldName); + } + } + + /** + * 提取 where 条件之前的真实表名。 + */ + private static String getTableName(String table) { + String tableName = table.trim().split("(?i)\\s+where\\s+", 2)[0].trim(); + return normalizeName(tableName.split("\\s+", 2)[0]); + } + + /** + * 统一表名,兼容大小写及限定名前缀。 + * + * @param name 表名 + * @return 标准名称 + */ + private static String normalizeName(String name) { + String normalizedName = name.trim().replace("`", "").replace("\"", "").toLowerCase(Locale.ROOT); + int separatorIndex = normalizedName.lastIndexOf('.'); + return separatorIndex >= 0 ? normalizedName.substring(separatorIndex + 1) : normalizedName; + } +} diff --git a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/SqlInjectionUtil.java b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/SqlInjectionUtil.java index 77560ff0d..a7c9ee227 100644 --- a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/SqlInjectionUtil.java +++ b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/SqlInjectionUtil.java @@ -7,6 +7,7 @@ import org.jeecg.common.constant.SymbolConstant; import org.jeecg.common.exception.JeecgSqlInjectionException; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -314,6 +315,39 @@ public class SqlInjectionUtil { return; } + /** + * 【issues/9840】校验前端请求传入的字典条件 SQL。 + *

+ * 处理规则: + * 1. 仅允许字段与常量组成比较、IN、LIKE、BETWEEN、IS NULL 条件,并支持括号、AND/OR 及安全排序; + * 2. 禁止子查询、函数、字段间比较、SQL 注释、追加语句、未解析占位符及非法排序表达式; + * 3. 从条件语法树提取字段并执行敏感字段校验,字典返回字段由服务入口执行相同校验; + * 4. 敏感字段清单由 SensitiveTableCheckUtil 统一维护,非敏感字段保持原有字典能力。 + *

+ * + * @param table 查询表名 + * @param value 字典过滤条件 + * @author scott + * @since 2026-08-25 issues/9840 字典过滤条件 SQL 注入防护 + */ + public static void filterDictConditionSqlFromRequest(String table, String value) { + if (value == null || "".equals(value)) { + return; + } + String trimmed = value.trim(); + if (trimmed.isEmpty()) { + return; + } + Set conditionFields; + try { + conditionFields = DictSqlConditionCheckUtil.checkAndGetFields(trimmed); + } catch (Exception e) { + log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value); + throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value); + } + SensitiveTableCheckUtil.checkForbiddenFields(table, conditionFields.toArray(new String[0])); + } + /** * 【提醒:不通用】 * 仅用于Online报表SQL解析,注入过滤 diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysDictServiceImpl.java b/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysDictServiceImpl.java index 50f937162..2624943f3 100644 --- a/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysDictServiceImpl.java +++ b/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysDictServiceImpl.java @@ -22,6 +22,7 @@ import org.jeecg.common.system.vo.DictModelMany; import org.jeecg.common.system.vo.DictQuery; import org.jeecg.common.util.CommonUtils; import org.jeecg.common.util.RedisUtil; +import org.jeecg.common.util.SensitiveTableCheckUtil; import org.jeecg.common.util.SqlInjectionUtil; import org.jeecg.common.util.dynamic.db.DbTypeUtils; import org.jeecg.common.util.oConvertUtils; @@ -275,6 +276,7 @@ public class SysDictServiceImpl extends ServiceImpl impl @Deprecated public List queryTableDictItemsByCode(String tableFilterSql, String text, String code) { log.debug("无缓存dictTableList的时候调用这里!"); + SensitiveTableCheckUtil.checkForbiddenFields(tableFilterSql, text, code); String str = tableFilterSql+","+text+","+code; // 【QQYUN-6533】表字典白名单check sysBaseAPI.dictTableWhiteListCheckByDict(tableFilterSql, text, code); @@ -297,7 +299,7 @@ public class SysDictServiceImpl extends ServiceImpl impl // 3.SQL注入check SqlInjectionUtil.filterContentMulti(table, text, code); - SqlInjectionUtil.specialFilterContentForDictSql(filterSql); + SqlInjectionUtil.filterDictConditionSqlFromRequest(table, filterSql); // 4.针对采用 ${}写法的表名和字段进行转义和check table = SqlInjectionUtil.getSqlInjectTableName(table); @@ -312,11 +314,12 @@ public class SysDictServiceImpl extends ServiceImpl impl @Override public List queryTableDictItemsByCodeAndFilter(String table, String text, String code, String filterSql) { log.debug("无缓存dictTableList的时候调用这里!"); + SensitiveTableCheckUtil.checkForbiddenFields(table, text, code); // 1.SQL注入校验(只限制非法串改数据库) SqlInjectionUtil.specialFilterContentForDictSql(table); SqlInjectionUtil.filterContentMulti(text, code); - SqlInjectionUtil.specialFilterContentForDictSql(filterSql); + SqlInjectionUtil.filterDictConditionSqlFromRequest(table, filterSql); String str = table+","+text+","+code; // 【QQYUN-6533】表字典白名单check @@ -348,6 +351,7 @@ public class SysDictServiceImpl extends ServiceImpl impl @Cacheable(value = CacheConstant.SYS_DICT_TABLE_CACHE, unless = "#result == null ") public String queryTableDictTextByKey(String table,String text,String code, String key) { log.debug("无缓存dictTable的时候调用这里!"); + SensitiveTableCheckUtil.checkForbiddenFields(table, text, code); String str = table+","+text+","+code; // 【QQYUN-6533】表字典白名单check @@ -378,6 +382,7 @@ public class SysDictServiceImpl extends ServiceImpl impl @Override public List queryTableDictTextByKeys(String table, String text, String code, List codeValues, String dataSource) { + SensitiveTableCheckUtil.checkForbiddenFields(table, text, code); String str = table+","+text+","+code; //update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------ // 是否自定义数据源 @@ -403,7 +408,7 @@ public class SysDictServiceImpl extends ServiceImpl impl // 3.SQL注入check SqlInjectionUtil.filterContentMulti(table, text, code); - SqlInjectionUtil.specialFilterContentForDictSql(filterSql); + SqlInjectionUtil.filterDictConditionSqlFromRequest(table, filterSql); // 4.针对采用 ${}写法的表名和字段进行转义和check table = SqlInjectionUtil.getSqlInjectTableName(table); @@ -459,6 +464,7 @@ public class SysDictServiceImpl extends ServiceImpl impl if(oConvertUtils.isEmpty(codeValuesStr)){ return null; } + SensitiveTableCheckUtil.checkForbiddenFields(table, text, code); //1.分割sql获取表名 和 条件sql String filterSql = null; @@ -470,7 +476,7 @@ public class SysDictServiceImpl extends ServiceImpl impl // 2.SQL注入check SqlInjectionUtil.filterContentMulti(table, text, code); - SqlInjectionUtil.specialFilterContentForDictSql(filterSql); + SqlInjectionUtil.filterDictConditionSqlFromRequest(table, filterSql); String str = table+","+text+","+code; // 【QQYUN-6533】表字典白名单check @@ -554,6 +560,7 @@ public class SysDictServiceImpl extends ServiceImpl impl @Override public List queryLittleTableDictItems(String tableSql, String text, String code, String condition, String keyword, int pageNo, int pageSize) { + SensitiveTableCheckUtil.checkForbiddenFields(tableSql, text, code); int current = oConvertUtils.getInt(pageNo, 1); Page page = new Page(current, pageSize); page.setSearchCount(false); @@ -582,6 +589,7 @@ public class SysDictServiceImpl extends ServiceImpl impl * @return */ private String getFilterSql(String tableSql, String text, String code, String condition, String keyword){ + String tableName = CommonUtils.getTableNameByTableSql(tableSql); String filterSql = ""; String keywordSql = null; String sqlWhere = "where "; @@ -642,8 +650,8 @@ public class SysDictServiceImpl extends ServiceImpl impl // 1.1 返回条件SQL(去掉开头的 where ) final String wherePrefix = "(?i)where "; // (?i) 表示不区分大小写 String filterSqlString = filterSql.trim().replaceAll(wherePrefix, ""); - // 1.2 条件SQL进行漏洞 check - SqlInjectionUtil.specialFilterContentForDictSql(filterSqlString); + // 1.2 统一校验最终的条件和排序 + SqlInjectionUtil.filterDictConditionSqlFromRequest(tableName, filterSqlString); // 1.3 判断如何返回条件是 order by开头则前面拼上 1=1 if (oConvertUtils.isNotEmpty(filterSqlString) && filterSqlString.trim().toUpperCase().startsWith("ORDER")) { filterSqlString = " 1=1 " + filterSqlString; @@ -654,14 +662,16 @@ public class SysDictServiceImpl extends ServiceImpl impl @Override public List queryAllTableDictItems(String table, String text, String code, String condition, String keyword) { + SensitiveTableCheckUtil.checkForbiddenFields(table, text, code); + // 拼接关键词条件前先校验字段,避免未经处理的字段进入 filterSql + text = SqlInjectionUtil.getSqlInjectField(text); + code = SqlInjectionUtil.getSqlInjectField(code); // 1.获取条件sql String filterSql = getFilterSql(table, text, code, condition, keyword); // 为了防止sql(jeecg提供了防注入的方法,可以在拼接 SQL 语句时自动对参数进行转义,避免SQL注入攻击) // 2.针对采用 ${}写法的表名和字段进行转义和check table = SqlInjectionUtil.getSqlInjectTableName(table); - text = SqlInjectionUtil.getSqlInjectField(text); - code = SqlInjectionUtil.getSqlInjectField(code); List ls = baseMapper.queryTableDictWithFilter(table, text, code, filterSql); return ls; @@ -669,6 +679,7 @@ public class SysDictServiceImpl extends ServiceImpl impl @Override public List queryTreeList(Map query, String table, String text, String code, String pidField, String pid, String hasChildField, int converIsLeafVal) { + SensitiveTableCheckUtil.checkForbiddenFields(table, text, code, pidField, hasChildField); //为了防止sql(jeecg提供了防注入的方法,可以在拼接 SQL 语句时自动对参数进行转义,避免SQL注入攻击) // 1.针对采用 ${}写法的表名和字段进行转义和check //update-begin---author:chenrui ---date:20251015 for:[QQYUN-13741]【客户问题 南自】online表单自定义树 表后边加条件时 不生效------------ @@ -694,7 +705,7 @@ public class SysDictServiceImpl extends ServiceImpl impl // 2.检测最终SQL是否存在SQL注入风险 String dictCode = table + "," + text + "," + code; SqlInjectionUtil.filterContentMulti(dictCode); - SqlInjectionUtil.specialFilterContentForDictSql(filterSql); + SqlInjectionUtil.filterDictConditionSqlFromRequest(table, filterSql); // 【QQYUN-6533】表字典白名单check sysBaseAPI.dictTableWhiteListCheckByDict(table, text, code); @@ -708,6 +719,7 @@ public class SysDictServiceImpl extends ServiceImpl impl if (query != null) { for (Map.Entry searchItem : query.entrySet()) { String fieldName = searchItem.getKey(); + SensitiveTableCheckUtil.checkForbiddenFields(table, fieldName); // update-begin---author:sjlei---date:20260413 for:【#9524】修复 SQL _tableFilterSql 注入漏洞 // _tableFilterSql 是服务端内部专用 key,对应 Mapper 中的 ${value} 裸拼接, // 禁止从外部 condition 参数传入,防止 SQL 注入(#9520) @@ -751,6 +763,7 @@ public class SysDictServiceImpl extends ServiceImpl impl @Override public List queryDictTablePageList(DictQuery query, int pageSize, int pageNo) { + SensitiveTableCheckUtil.checkForbiddenFields(query.getTable(), query.getText(), query.getCode()); Page page = new Page(pageNo,pageSize,false); //为了防止sql(jeecg提供了防注入的方法,可以在拼接 SQL 语句时自动对参数进行转义,避免SQL注入攻击) @@ -821,9 +834,6 @@ public class SysDictServiceImpl extends ServiceImpl impl return null; } - // 2.字典SQL注入风险check - SqlInjectionUtil.specialFilterContentForDictSql(dictCode); - if (dictCode.contains(SymbolConstant.COMMA)) { // 代码逻辑说明: 下拉搜索不支持表名后加查询条件 String[] params = dictCode.split(","); @@ -882,6 +892,7 @@ public class SysDictServiceImpl extends ServiceImpl impl allFieldList.add(f.trim()); } } + SensitiveTableCheckUtil.checkForbiddenFields(tableName, allFieldList.toArray(new String[0])); sysBaseAPI.dictTableWhiteListCheckByDict(tableName, allFieldList.toArray(new String[0])); // 1.2 SQL 注入基础检查 @@ -933,7 +944,7 @@ public class SysDictServiceImpl extends ServiceImpl impl String filterSql = conditionParts.isEmpty() ? "" : String.join(" and ", conditionParts); if (oConvertUtils.isNotEmpty(filterSql)) { - SqlInjectionUtil.specialFilterContentForDictSql(filterSql); + SqlInjectionUtil.filterDictConditionSqlFromRequest(tableName, filterSql); } // ---------- 4. 分页查询 ---------- diff --git a/jeecgboot-vue3/src/views/demo/jeecg/JeecgComponents.vue b/jeecgboot-vue3/src/views/demo/jeecg/JeecgComponents.vue index 609fd43e9..a06827623 100644 --- a/jeecgboot-vue3/src/views/demo/jeecg/JeecgComponents.vue +++ b/jeecgboot-vue3/src/views/demo/jeecg/JeecgComponents.vue @@ -1,4 +1,29 @@