소스 검색

fix(security): 修复SQL注入风险

对 category/sortOrder/排序字段增加白名单校验,防止动态SQL拼接注入。

Co-authored-by: Cursor <cursoragent@cursor.com>
吴树波 21 시간 전
부모
커밋
9d0c68c9d2

+ 5 - 3
fs-common/src/main/java/com/fs/common/utils/SortUtils.java

@@ -1,6 +1,7 @@
 package com.fs.common.utils;
 
 import com.fs.common.core.domain.SortRule;
+import com.fs.common.utils.security.SqlSafeUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -26,11 +27,12 @@ public class SortUtils {
             }
 
             String field = split[0].trim().replace(" ", ""); // 字段名:a, b, c
+            // 防止排序字段名 SQL 注入:仅允许字母数字下划线
+            field = SqlSafeUtils.validateColumnName(field);
 
             String direction = split[1].replace(")", "").trim(); // 方向:desc
-            if (!"asc".equalsIgnoreCase(direction) && !"desc".equalsIgnoreCase(direction)) {
-                continue;
-            }
+            // 排序方向白名单
+            direction = SqlSafeUtils.validateOrderDirection(direction);
 
             rules.add(new SortRule(field, direction));
         }

+ 67 - 0
fs-common/src/main/java/com/fs/common/utils/security/SqlSafeUtils.java

@@ -0,0 +1,67 @@
+package com.fs.common.utils.security;
+
+import com.fs.common.exception.ServiceException;
+import com.fs.common.utils.StringUtils;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * SQL 动态片段安全校验工具(排序方向、列名白名单)
+ */
+public final class SqlSafeUtils {
+
+    private static final Set<String> ORDER_DIRECTIONS = new HashSet<String>(Arrays.asList("asc", "desc"));
+
+    /** 直播趋势统计允许的 category 列名 */
+    private static final Set<String> LIVE_CATEGORY_COLUMNS = new HashSet<String>(Arrays.asList(
+            "page_views", "unique_visitors", "total_views", "unique_viewers", "streams"
+    ));
+
+    private SqlSafeUtils() {
+    }
+
+    /**
+     * 校验排序方向,仅允许 asc/desc;空值直接返回
+     */
+    public static String validateOrderDirection(String order) {
+        if (StringUtils.isEmpty(order)) {
+            return order;
+        }
+        String lower = order.trim().toLowerCase(Locale.ROOT);
+        if (!ORDER_DIRECTIONS.contains(lower)) {
+            throw new ServiceException("排序方向非法,仅支持 asc/desc");
+        }
+        return lower;
+    }
+
+    /**
+     * 校验直播趋势 category,防止 ${category} SQL 注入
+     */
+    public static String validateLiveCategory(String category) {
+        if (StringUtils.isEmpty(category)) {
+            throw new ServiceException("category 不能为空");
+        }
+        String value = category.trim();
+        if (!LIVE_CATEGORY_COLUMNS.contains(value)) {
+            throw new ServiceException("非法的 category 参数");
+        }
+        return value;
+    }
+
+    /**
+     * 校验排序字段名:仅允许字母数字下划线,且长度受限
+     */
+    public static String validateColumnName(String column) {
+        if (StringUtils.isEmpty(column)) {
+            throw new ServiceException("排序字段不能为空");
+        }
+        String value = column.trim();
+        if (value.length() > 64 || !value.matches("^[a-zA-Z0-9_]+$")) {
+            throw new ServiceException("排序字段名非法: " + column);
+        }
+        return value;
+    }
+}

+ 8 - 0
fs-service/src/main/java/com/fs/his/param/FsStoreOrderParam.java

@@ -3,6 +3,7 @@ package com.fs.his.param;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.BaseEntity;
+import com.fs.common.utils.security.SqlSafeUtils;
 import com.fs.his.vo.FsStoreOrderExportVO;
 import lombok.Data;
 
@@ -253,4 +254,11 @@ public class FsStoreOrderParam extends BaseEntity implements Serializable {
      * 商户号(merchant_app_config.merchant_id)
      */
     private String merchantId;
+
+    /**
+     * 排序方向白名单校验,防止 ${sortOrder} SQL 注入
+     */
+    public void setSortOrder(String sortOrder) {
+        this.sortOrder = SqlSafeUtils.validateOrderDirection(sortOrder);
+    }
 }

+ 8 - 0
fs-service/src/main/java/com/fs/hisStore/param/FsStoreOrderParam.java

@@ -2,6 +2,7 @@ package com.fs.hisStore.param;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fs.common.core.domain.BaseEntity;
+import com.fs.common.utils.security.SqlSafeUtils;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -129,4 +130,11 @@ public class FsStoreOrderParam extends BaseEntity implements Serializable
 
     // 多个销售id
     private List<Long> companyUserIds;
+
+    /**
+     * 排序方向白名单校验,防止 ${sortOrder} SQL 注入
+     */
+    public void setSortOrder(String sortOrder) {
+        this.sortOrder = SqlSafeUtils.validateOrderDirection(sortOrder);
+    }
 }

+ 8 - 0
fs-service/src/main/java/com/fs/live/param/LiveOrderParam.java

@@ -2,6 +2,7 @@ package com.fs.live.param;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fs.common.core.domain.BaseEntity;
+import com.fs.common.utils.security.SqlSafeUtils;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -114,4 +115,11 @@ public class LiveOrderParam extends BaseEntity implements Serializable
     //银行交易流水号
     private String bankTransactionId;
 
+    /**
+     * 排序方向白名单校验,防止 ${sortOrder} SQL 注入
+     */
+    public void setSortOrder(String sortOrder) {
+        this.sortOrder = SqlSafeUtils.validateOrderDirection(sortOrder);
+    }
+
 }

+ 4 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveDataServiceImpl.java

@@ -358,6 +358,8 @@ public class LiveDataServiceImpl implements ILiveDataService {
 
     @Override
     public TrendDataVO getCompanyTrendData(String type, String selectedDate, String category, Long companyId) {
+        // 白名单校验 category,防止 ${category} SQL 注入
+        category = com.fs.common.utils.security.SqlSafeUtils.validateLiveCategory(category);
         //DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
         LocalDate date = LocalDate.parse(selectedDate, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
         DateRange currentRange = getDateRange(type, date, 0);
@@ -423,6 +425,8 @@ public class LiveDataServiceImpl implements ILiveDataService {
      */
     @Override
     public TrendDataVO getTrendData(String type, String selectedDate,String category) {
+        // 白名单校验 category,防止 ${category} SQL 注入
+        category = com.fs.common.utils.security.SqlSafeUtils.validateLiveCategory(category);
         //DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
         LocalDate date = LocalDate.parse(selectedDate, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
         DateRange currentRange = getDateRange(type, date, 0);