Commit cab59d4dd8fd1644f3b5137e2b851417f055d9fc

Authored by YunaiV
1 parent ff8a5241

fix(framework): 修复分页排序 SQL 注入风险

yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml
... ... @@ -105,6 +105,13 @@
105 105 <groupId>com.fhs-opensource</groupId>
106 106 <artifactId>easy-trans-mybatis-plus-extend</artifactId>
107 107 </dependency>
  108 +
  109 + <!-- Test 测试相关 -->
  110 + <dependency>
  111 + <groupId>org.springframework.boot</groupId>
  112 + <artifactId>spring-boot-starter-test</artifactId>
  113 + <scope>test</scope>
  114 + </dependency>
108 115 </dependencies>
109 116  
110 117 </project>
... ...
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;
23 23 import java.util.ArrayList;
24 24 import java.util.Collection;
25 25 import java.util.List;
  26 +import java.util.regex.Pattern;
26 27  
27 28 /**
28 29 * MyBatis 工具类
... ... @@ -31,6 +32,8 @@ public class MyBatisUtils {
31 32  
32 33 private static final String MYSQL_ESCAPE_CHARACTER = "`";
33 34  
  35 + private static final Pattern SAFE_COLUMN_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_]+(\\.[a-zA-Z0-9_]+)*$");
  36 +
34 37 public static <T> Page<T> buildPage(PageParam pageParam) {
35 38 return buildPage(pageParam, null);
36 39 }
... ... @@ -42,8 +45,11 @@ public class MyBatisUtils {
42 45 // 排序字段
43 46 if (CollUtil.isNotEmpty(sortingFields)) {
44 47 for (SortingField sortingField : sortingFields) {
45   - page.addOrder(new OrderItem().setAsc(SortingField.ORDER_ASC.equals(sortingField.getOrder()))
46   - .setColumn(StrUtil.toUnderlineCase(sortingField.getField())));
  48 + String columnName = buildSafeOrderColumn(sortingField.getField());
  49 + if (columnName == null) {
  50 + continue;
  51 + }
  52 + page.addOrder(new OrderItem().setAsc(isAscOrder(sortingField.getOrder())).setColumn(columnName));
47 53 }
48 54 }
49 55 return page;
... ... @@ -57,23 +63,29 @@ public class MyBatisUtils {
57 63 if (wrapper instanceof QueryWrapper<T>) {
58 64 QueryWrapper<T> query = (QueryWrapper<T>) wrapper;
59 65 for (SortingField sortingField : sortingFields) {
60   - query.orderBy(true,
61   - SortingField.ORDER_ASC.equals(sortingField.getOrder()),
62   - StrUtil.toUnderlineCase(sortingField.getField()));
  66 + String columnName = buildSafeOrderColumn(sortingField.getField());
  67 + if (columnName == null) {
  68 + continue;
  69 + }
  70 + query.orderBy(true, isAscOrder(sortingField.getOrder()), columnName);
63 71 }
64 72 } else if (wrapper instanceof LambdaQueryWrapper<T>) {
65 73 // LambdaQueryWrapper 不直接支持字符串字段排序,使用 last 方法拼接 ORDER BY
66 74 LambdaQueryWrapper<T> lambdaQuery = (LambdaQueryWrapper<T>) wrapper;
67 75 StringBuilder orderBy = new StringBuilder();
68 76 for (SortingField sortingField : sortingFields) {
  77 + String columnName = buildSafeOrderColumn(sortingField.getField());
  78 + if (columnName == null) {
  79 + continue;
  80 + }
69 81 if (StrUtil.isNotEmpty(orderBy)) {
70 82 orderBy.append(", ");
71 83 }
72   - orderBy.append(StrUtil.toUnderlineCase(sortingField.getField()))
73   - .append(" ")
74   - .append(SortingField.ORDER_ASC.equals(sortingField.getOrder()) ? "ASC" : "DESC");
  84 + orderBy.append(columnName).append(" ").append(getOrderDirection(sortingField.getOrder()));
  85 + }
  86 + if (StrUtil.isNotEmpty(orderBy)) {
  87 + lambdaQuery.last("ORDER BY " + orderBy);
75 88 }
76   - lambdaQuery.last("ORDER BY " + orderBy);
77 89 // 另外个思路:https://blog.csdn.net/m0_59084856/article/details/138450913
78 90 } else {
79 91 throw new IllegalArgumentException("Unsupported wrapper type: " + wrapper.getClass().getName());
... ... @@ -81,6 +93,22 @@ public class MyBatisUtils {
81 93  
82 94 }
83 95  
  96 + public static boolean isAscOrder(String order) {
  97 + return SortingField.ORDER_ASC.equals(order);
  98 + }
  99 +
  100 + public static String getOrderDirection(String order) {
  101 + return isAscOrder(order) ? "ASC" : "DESC";
  102 + }
  103 +
  104 + private static String buildSafeOrderColumn(String field) {
  105 + String columnName = StrUtil.toUnderlineCase(field);
  106 + if (StrUtil.isEmpty(columnName) || !SAFE_COLUMN_NAME_PATTERN.matcher(columnName).matches()) {
  107 + return null;
  108 + }
  109 + return columnName;
  110 + }
  111 +
84 112 /**
85 113 * 将拦截器添加到链中
86 114 * 由于 MybatisPlusInterceptor 不支持添加拦截器,所以只能全量设置
... ...
yudao-module-mall/yudao-module-trade/src/main/resources/mapper/brokerage/BrokerageUserMapper.xml
... ... @@ -2,9 +2,19 @@
2 2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
3 3 <mapper namespace="cn.iocoder.yudao.module.trade.dal.mysql.brokerage.BrokerageUserMapper">
4 4  
  5 + <sql id="OrderDirection">
  6 + <choose>
  7 + <when test="@cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils@isAscOrder(sortingField.order)">
  8 + ASC
  9 + </when>
  10 + <otherwise>
  11 + DESC
  12 + </otherwise>
  13 + </choose>
  14 + </sql>
  15 +
5 16 <select id="selectSummaryPageByUserId"
6 17 resultType="cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryRespVO">
7   -
8 18 SELECT bu.id, bu.bind_user_time AS brokerageTime,
9 19 (SELECT SUM(price) FROM trade_brokerage_record r
10 20 WHERE r.user_id = bu.id AND biz_type = #{bizType} AND r.status = #{status} AND r.deleted = FALSE) AS brokeragePrice,
... ... @@ -23,14 +33,17 @@
23 33 </if>
24 34 </where>
25 35 <choose>
26   - <when test="sortingField.field == 'userCount'">
27   - ORDER BY brokerageUserCount ${sortingField.order}
  36 + <when test="sortingField != null and sortingField.field == 'userCount'">
  37 + ORDER BY brokerageUserCount
  38 + <include refid="OrderDirection"/>
28 39 </when>
29   - <when test="sortingField.field == 'orderCount'">
30   - ORDER BY brokerageOrderCount ${sortingField.order}
  40 + <when test="sortingField != null and sortingField.field == 'orderCount'">
  41 + ORDER BY brokerageOrderCount
  42 + <include refid="OrderDirection"/>
31 43 </when>
32   - <when test="sortingField.field == 'price'">
33   - ORDER BY brokeragePrice ${sortingField.order}
  44 + <when test="sortingField != null and sortingField.field == 'price'">
  45 + ORDER BY brokeragePrice
  46 + <include refid="OrderDirection"/>
34 47 </when>
35 48 <otherwise>
36 49 ORDER BY bu.bind_user_time DESC
... ...