Skip to content

Commit

Permalink
refactor(curd): 重构排序字段处理,预防 SQL 注入问题
Browse files Browse the repository at this point in the history
  • Loading branch information
Charles7c committed Aug 13, 2024
1 parent 4786af5 commit c31fa75
Show file tree
Hide file tree
Showing 13 changed files with 274 additions and 229 deletions.
5 changes: 5 additions & 0 deletions continew-starter-data/continew-starter-data-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,10 @@
<groupId>cn.hutool</groupId>
<artifactId>hutool-db</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package top.continew.starter.data.core.util;

import java.util.Objects;
import java.util.regex.Pattern;

/**
* SQL 注入验证工具类
*
* @author hubin
* @since 2.5.2
*/
public class SqlInjectionUtils {

/**
* SQL语法检查正则:符合两个关键字(有先后顺序)才算匹配
*/
private static final Pattern SQL_SYNTAX_PATTERN = Pattern
.compile("(insert|delete|update|select|create|drop|truncate|grant|alter|deny|revoke|call|execute|exec|declare|show|rename|set)" + "\\s+.*(into|from|set|where|table|database|view|index|on|cursor|procedure|trigger|for|password|union|and|or)|(select\\s*\\*\\s*from\\s+)|(and|or)\\s+.*", Pattern.CASE_INSENSITIVE);

/**
* 使用'、;或注释截断SQL检查正则
*/
private static final Pattern SQL_COMMENT_PATTERN = Pattern
.compile("'.*(or|union|--|#|/\\*|;)", Pattern.CASE_INSENSITIVE);

/**
* 检查参数是否存在 SQL 注入
*
* @param value 检查参数
* @return true:非法;false:合法
*/
public static boolean check(String value) {
Objects.requireNonNull(value);
// 处理是否包含 SQL 注释字符 || 检查是否包含 SQ L注入敏感字符
return SQL_COMMENT_PATTERN.matcher(value).find() || SQL_SYNTAX_PATTERN.matcher(value).find();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,23 @@
* limitations under the License.
*/

package top.continew.starter.data.mybatis.flex.query;
package top.continew.starter.data.mybatis.flex.util;

import com.mybatisflex.core.query.QueryWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.mybatisflex.core.query.QueryWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Sort;
import top.continew.starter.core.exception.BadRequestException;
import top.continew.starter.core.util.ReflectUtils;
import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.data.core.annotation.Query;
import top.continew.starter.data.core.annotation.QueryIgnore;
import top.continew.starter.data.core.enums.QueryType;
import top.continew.starter.data.core.util.SqlInjectionUtils;

import java.lang.reflect.Field;
import java.util.ArrayList;
Expand Down Expand Up @@ -56,15 +58,42 @@ private QueryWrapperHelper() {
*
* @param query 查询条件
* @param <Q> 查询条件数据类型
* @param <R> 查询数据类型
* @return QueryWrapper
*/
public static <Q, R> QueryWrapper build(Q query) {
public static <Q> QueryWrapper build(Q query) {
QueryWrapper queryWrapper = QueryWrapper.create();
// 没有查询条件,直接返回
if (null == query) {
return queryWrapper;
}
// 获取查询条件中所有的字段
List<Field> fieldList = ReflectUtils.getNonStaticFields(query.getClass());
return build(query, fieldList, queryWrapper);
}

/**
* 构建 QueryWrapper
*
* @param query 查询条件
* @param sort 排序条件
* @param <Q> 查询条件数据类型
* @return QueryWrapper
* @since 2.5.2
*/
public static <Q> QueryWrapper build(Q query, Sort sort) {
QueryWrapper queryWrapper = QueryWrapper.create();
// 没有查询条件,直接返回
if (null == query) {
return queryWrapper;
}
// 设置排序条件
if (sort != null && sort.isSorted()) {
for (Sort.Order order : sort) {
String field = CharSequenceUtil.toUnderlineCase(order.getProperty());
ValidationUtils.throwIf(SqlInjectionUtils.check(field), "排序字段包含非法字符");
queryWrapper.orderBy(field, order.isAscending());
}
}
// 获取查询条件中所有的字段
List<Field> fieldList = ReflectUtils.getNonStaticFields(query.getClass());
return build(query, fieldList, queryWrapper);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

package top.continew.starter.data.mybatis.plus.query;
package top.continew.starter.data.mybatis.plus.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -23,18 +23,17 @@
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.data.domain.Sort;
import top.continew.starter.core.exception.BadRequestException;
import top.continew.starter.core.util.ReflectUtils;
import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.data.core.annotation.Query;
import top.continew.starter.data.core.annotation.QueryIgnore;
import top.continew.starter.data.core.enums.QueryType;
import top.continew.starter.data.core.util.SqlInjectionUtils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.*;
import java.util.function.Consumer;

/**
Expand All @@ -60,11 +59,33 @@ private QueryWrapperHelper() {
* @return QueryWrapper
*/
public static <Q, R> QueryWrapper<R> build(Q query) {
return build(query, Sort.unsorted());
}

/**
* 构建 QueryWrapper
*
* @param query 查询条件
* @param sort 排序条件
* @param <Q> 查询条件数据类型
* @param <R> 查询数据类型
* @return QueryWrapper
* @since 2.5.2
*/
public static <Q, R> QueryWrapper<R> build(Q query, Sort sort) {
QueryWrapper<R> queryWrapper = new QueryWrapper<>();
// 没有查询条件,直接返回
if (null == query) {
return queryWrapper;
}
// 设置排序条件
if (sort != null && sort.isSorted()) {
for (Sort.Order order : sort) {
String field = CharSequenceUtil.toUnderlineCase(order.getProperty());
ValidationUtils.throwIf(SqlInjectionUtils.check(field), "排序字段包含非法字符");
queryWrapper.orderBy(true, order.isAscending(), field);
}
}
// 获取查询条件中所有的字段
List<Field> fieldList = ReflectUtils.getNonStaticFields(query.getClass());
return build(query, fieldList, queryWrapper);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
<artifactId>spring-data-commons</artifactId>
</dependency>

<!-- Web 模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-web</artifactId>
</dependency>

<!-- 认证模块 - SaToken -->
<dependency>
<groupId>top.continew</groupId>
Expand All @@ -36,16 +42,16 @@
</exclusions>
</dependency>

<!-- 文件处理模块 - Excel -->
<!-- 数据访问模块 - 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-file-excel</artifactId>
<artifactId>continew-starter-data-core</artifactId>
</dependency>

<!-- Web 模块 -->
<!-- 文件处理模块 - Excel -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-web</artifactId>
<artifactId>continew-starter-file-excel</artifactId>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@

package top.continew.starter.extension.crud.model.query;

import cn.hutool.core.net.URLDecoder;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ArrayUtil;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.data.domain.Sort;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.data.core.util.SqlInjectionUtils;

import java.io.Serial;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

Expand Down Expand Up @@ -54,25 +58,34 @@ public Sort getSort() {
if (ArrayUtil.isEmpty(sort)) {
return Sort.unsorted();
}

ValidationUtils.throwIf(sort.length < 2, "排序条件非法");
List<Sort.Order> orders = new ArrayList<>(sort.length);
if (CharSequenceUtil.contains(sort[0], StringConstants.COMMA)) {
if (CharSequenceUtil.contains(sort[0], URLDecoder.decode(StringConstants.COMMA, StandardCharsets.UTF_8))) {
// e.g "sort=createTime,desc&sort=name,asc"
for (String s : sort) {
List<String> sortList = CharSequenceUtil.splitTrim(s, StringConstants.COMMA);
Sort.Order order = new Sort.Order(Sort.Direction.valueOf(sortList.get(1).toUpperCase()), sortList
.get(0));
orders.add(order);
orders.add(this.getOrder(sortList.get(0), sortList.get(1)));
}
} else {
// e.g "sort=createTime,desc"
Sort.Order order = new Sort.Order(Sort.Direction.valueOf(sort[1].toUpperCase()), sort[0]);
orders.add(order);
orders.add(this.getOrder(sort[0], sort[1]));
}
return Sort.by(orders);
}

public void setSort(String[] sort) {
this.sort = sort;
}

/**
* 获取排序条件
*
* @param field 字段
* @param direction 排序方向
* @return 排序条件
*/
private Sort.Order getOrder(String field, String direction) {
ValidationUtils.throwIf(SqlInjectionUtils.check(field), "排序字段包含非法字符");
return new Sort.Order(Sort.Direction.valueOf(direction.toUpperCase()), field);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package top.continew.starter.extension.crud.model.resp;

import io.swagger.v3.oas.annotations.media.Schema;

import java.io.Serial;
import java.io.Serializable;
import java.util.List;

/**
* 分页信息
*
* @param <T> 列表数据类型
* @author Charles7c
* @since 2.5.2
*/
@Schema(description = "分页信息")
public class BasePageResp<T> implements Serializable {

@Serial
private static final long serialVersionUID = 1L;

/**
* 列表数据
*/
@Schema(description = "列表数据")
private List<T> list;

/**
* 总记录数
*/
@Schema(description = "总记录数", example = "10")
private long total;

public BasePageResp() {
}

public BasePageResp(final List<T> list, final long total) {
this.list = list;
this.total = total;
}

public List<T> getList() {
return list;
}

public void setList(List<T> list) {
this.list = list;
}

public long getTotal() {
return total;
}

public void setTotal(long total) {
this.total = total;
}
}
Loading

0 comments on commit c31fa75

Please sign in to comment.