From cab59d4dd8fd1644f3b5137e2b851417f055d9fc Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 23 May 2026 12:26:57 +0800 Subject: [PATCH] fix(framework): 修复分页排序 SQL 注入风险 --- yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml | 7 +++++++ yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java | 46 +++++++++++++++++++++++++++++++++++++--------- yudao-module-mall/yudao-module-trade/src/main/resources/mapper/brokerage/BrokerageUserMapper.xml | 27 ++++++++++++++++++++------- 3 files changed, 64 insertions(+), 16 deletions(-) diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml b/yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml index 3272059..c4da560 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml @@ -105,6 +105,13 @@ com.fhs-opensource easy-trans-mybatis-plus-extend + + + + org.springframework.boot + spring-boot-starter-test + test + diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java index 2b4d138..a239301 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java @@ -23,6 +23,7 @@ import net.sf.jsqlparser.schema.Table; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.regex.Pattern; /** * MyBatis 工具类 @@ -31,6 +32,8 @@ public class MyBatisUtils { private static final String MYSQL_ESCAPE_CHARACTER = "`"; + private static final Pattern SAFE_COLUMN_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_]+(\\.[a-zA-Z0-9_]+)*$"); + public static Page buildPage(PageParam pageParam) { return buildPage(pageParam, null); } @@ -42,8 +45,11 @@ public class MyBatisUtils { // 排序字段 if (CollUtil.isNotEmpty(sortingFields)) { for (SortingField sortingField : sortingFields) { - page.addOrder(new OrderItem().setAsc(SortingField.ORDER_ASC.equals(sortingField.getOrder())) - .setColumn(StrUtil.toUnderlineCase(sortingField.getField()))); + String columnName = buildSafeOrderColumn(sortingField.getField()); + if (columnName == null) { + continue; + } + page.addOrder(new OrderItem().setAsc(isAscOrder(sortingField.getOrder())).setColumn(columnName)); } } return page; @@ -57,23 +63,29 @@ public class MyBatisUtils { if (wrapper instanceof QueryWrapper) { QueryWrapper query = (QueryWrapper) wrapper; for (SortingField sortingField : sortingFields) { - query.orderBy(true, - SortingField.ORDER_ASC.equals(sortingField.getOrder()), - StrUtil.toUnderlineCase(sortingField.getField())); + String columnName = buildSafeOrderColumn(sortingField.getField()); + if (columnName == null) { + continue; + } + query.orderBy(true, isAscOrder(sortingField.getOrder()), columnName); } } else if (wrapper instanceof LambdaQueryWrapper) { // LambdaQueryWrapper 不直接支持字符串字段排序,使用 last 方法拼接 ORDER BY LambdaQueryWrapper lambdaQuery = (LambdaQueryWrapper) wrapper; StringBuilder orderBy = new StringBuilder(); for (SortingField sortingField : sortingFields) { + String columnName = buildSafeOrderColumn(sortingField.getField()); + if (columnName == null) { + continue; + } if (StrUtil.isNotEmpty(orderBy)) { orderBy.append(", "); } - orderBy.append(StrUtil.toUnderlineCase(sortingField.getField())) - .append(" ") - .append(SortingField.ORDER_ASC.equals(sortingField.getOrder()) ? "ASC" : "DESC"); + orderBy.append(columnName).append(" ").append(getOrderDirection(sortingField.getOrder())); + } + if (StrUtil.isNotEmpty(orderBy)) { + lambdaQuery.last("ORDER BY " + orderBy); } - lambdaQuery.last("ORDER BY " + orderBy); // 另外个思路:https://blog.csdn.net/m0_59084856/article/details/138450913 } else { throw new IllegalArgumentException("Unsupported wrapper type: " + wrapper.getClass().getName()); @@ -81,6 +93,22 @@ public class MyBatisUtils { } + public static boolean isAscOrder(String order) { + return SortingField.ORDER_ASC.equals(order); + } + + public static String getOrderDirection(String order) { + return isAscOrder(order) ? "ASC" : "DESC"; + } + + private static String buildSafeOrderColumn(String field) { + String columnName = StrUtil.toUnderlineCase(field); + if (StrUtil.isEmpty(columnName) || !SAFE_COLUMN_NAME_PATTERN.matcher(columnName).matches()) { + return null; + } + return columnName; + } + /** * 将拦截器添加到链中 * 由于 MybatisPlusInterceptor 不支持添加拦截器,所以只能全量设置 diff --git a/yudao-module-mall/yudao-module-trade/src/main/resources/mapper/brokerage/BrokerageUserMapper.xml b/yudao-module-mall/yudao-module-trade/src/main/resources/mapper/brokerage/BrokerageUserMapper.xml index 066f75d..69ee4ed 100644 --- a/yudao-module-mall/yudao-module-trade/src/main/resources/mapper/brokerage/BrokerageUserMapper.xml +++ b/yudao-module-mall/yudao-module-trade/src/main/resources/mapper/brokerage/BrokerageUserMapper.xml @@ -2,9 +2,19 @@ + + + + ASC + + + DESC + + + +