---重构表字典逻辑,深度解决SQL注入漏洞问题,新旧版本都可以参考此修改合并---

(重点针对表名和字段进行单独check处理,更严格的格式要求,可能会导致一些特殊字典用法出问题,请根据自己业务做灵活调整)
org\jeecg\common\exception\JeecgSqlInjectionException.java(+)
org\jeecg\common\exception\JeecgBootExceptionHandler.java

org\jeecg\common\util\security\AbstractQueryBlackListHandler.java
org\jeecg\common\util\SqlInjectionUtil.java
org\jeecg\modules\system\controller\DuplicateCheckController.java
org\jeecg\modules\system\mapper\xml\SysDictMapper.xml
org\jeecg\modules\system\mapper\SysDictMapper.java
org\jeecg\modules\system\service\impl\SysDictServiceImpl.java
org\jeecg\modules\system\service\ISysDictService.java
pull/5377/head
zhangdaiscott 2023-09-03 20:07:58 +08:00
parent 58aebdbba4
commit 44952c79c2
9 changed files with 493 additions and 314 deletions

View File

@ -1,6 +1,7 @@
package org.jeecg.common.exception; package org.jeecg.common.exception;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.AuthorizationException; import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.authz.UnauthorizedException;
import org.jeecg.common.api.vo.Result; import org.jeecg.common.api.vo.Result;
@ -16,8 +17,6 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.NoHandlerFoundException;
import lombok.extern.slf4j.Slf4j;
/** /**
* *
* *
@ -133,4 +132,24 @@ public class JeecgBootExceptionHandler {
return Result.error("Redis 连接异常!"); return Result.error("Redis 连接异常!");
} }
/**
* SQL
*
* @param exception
* @return
*/
@ExceptionHandler(JeecgSqlInjectionException.class)
public Result<?> handleSQLException(Exception exception) {
String msg = exception.getMessage().toLowerCase();
final String extractvalue = "extractvalue";
final String updatexml = "updatexml";
boolean hasSensitiveInformation = msg.indexOf(extractvalue) >= 0 || msg.indexOf(updatexml) >= 0;
if (msg != null && hasSensitiveInformation) {
log.error("校验失败存在SQL注入风险{}", msg);
return Result.error("校验失败存在SQL注入风险");
}
return Result.error("校验失败存在SQL注入风险" + msg);
}
} }

View File

@ -0,0 +1,23 @@
package org.jeecg.common.exception;
/**
* @Description: jeecg-bootSQL
* @author: jeecg-boot
*/
public class JeecgSqlInjectionException extends RuntimeException {
private static final long serialVersionUID = 1L;
public JeecgSqlInjectionException(String message){
super(message);
}
public JeecgSqlInjectionException(Throwable cause)
{
super(cause);
}
public JeecgSqlInjectionException(String message, Throwable cause)
{
super(message,cause);
}
}

View File

@ -2,7 +2,10 @@ package org.jeecg.common.util;
import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.SecureUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.common.exception.JeecgBootException; import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.exception.JeecgSqlInjectionException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Set; import java.util.Set;
@ -47,7 +50,7 @@ public class SqlInjectionUtil {
* @param request: * @param request:
* @Return: void * @Return: void
*/ */
public static void checkDictTableSign(String dictCode, String sign, HttpServletRequest request) { private static void checkDictTableSign(String dictCode, String sign, HttpServletRequest request) {
//表字典SQL注入漏洞,签名校验 //表字典SQL注入漏洞,签名校验
String accessToken = request.getHeader("X-Access-Token"); String accessToken = request.getHeader("X-Access-Token");
String signStr = dictCode + SqlInjectionUtil.TABLE_DICT_SIGN_SALT + accessToken; String signStr = dictCode + SqlInjectionUtil.TABLE_DICT_SIGN_SALT + accessToken;
@ -60,11 +63,72 @@ public class SqlInjectionUtil {
} }
/** /**
*
* <p>
* sql * sql
* @param value *
* @param table
*/ */
public static void filterContent(String value) { private static Pattern tableNamePattern = Pattern.compile("^[a-zA-Z][a-zA-Z0-9_]{0,63}$");
filterContent(value, null); public static String getSqlInjectTableName(String table) {
table = table.trim();
/**
*
*
* 线
*
* 64
*/
boolean isValidTableName = tableNamePattern.matcher(table).matches();
if (!isValidTableName) {
String errorMsg = "表名不合法存在SQL注入风险!--->" + table;
log.error(errorMsg);
throw new JeecgSqlInjectionException(errorMsg);
}
//进一步验证是否存在SQL注入风险
filterContent(table);
return table;
}
/**
*
* <p>
* sql
*
* @param field
*/
static final Pattern fieldPattern = Pattern.compile("^[a-zA-Z0-9_]+$");
public static String getSqlInjectField(String field) {
field = field.trim();
if (field.contains(SymbolConstant.COMMA)) {
return getSqlInjectField(field.split(SymbolConstant.COMMA));
}
/**
*
*
* 线
*/
boolean isValidField = fieldPattern.matcher(field).matches();
if (!isValidField) {
String errorMsg = "字段不合法存在SQL注入风险!--->" + field;
log.error(errorMsg);
throw new JeecgSqlInjectionException(errorMsg);
}
//进一步验证是否存在SQL注入风险
filterContent(field);
return field;
}
public static String getSqlInjectField(String... fields) {
for (String s : fields) {
getSqlInjectField(s);
}
return String.join(SymbolConstant.COMMA, fields);
} }
/** /**
@ -89,7 +153,7 @@ public class SqlInjectionUtil {
if (value.indexOf(xssArr[i]) > -1) { if (value.indexOf(xssArr[i]) > -1) {
log.error("请注意存在SQL注入关键词---> {}", xssArr[i]); log.error("请注意存在SQL注入关键词---> {}", xssArr[i]);
log.error("请注意值可能存在SQL注入风险!---> {}", value); log.error("请注意值可能存在SQL注入风险!---> {}", value);
throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value); throw new JeecgSqlInjectionException("请注意值可能存在SQL注入风险!--->" + value);
} }
} }
//update-begin-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的还需要额外的校验比如 单引号 //update-begin-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的还需要额外的校验比如 单引号
@ -99,13 +163,13 @@ public class SqlInjectionUtil {
if (value.indexOf(xssArr2[i]) > -1) { if (value.indexOf(xssArr2[i]) > -1) {
log.error("请注意存在SQL注入关键词---> {}", xssArr2[i]); log.error("请注意存在SQL注入关键词---> {}", xssArr2[i]);
log.error("请注意值可能存在SQL注入风险!---> {}", value); log.error("请注意值可能存在SQL注入风险!---> {}", value);
throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value); throw new JeecgSqlInjectionException("请注意值可能存在SQL注入风险!--->" + value);
} }
} }
} }
//update-end-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的还需要额外的校验比如 单引号 //update-end-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的还需要额外的校验比如 单引号
if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){ if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value); throw new JeecgSqlInjectionException("请注意值可能存在SQL注入风险!--->" + value);
} }
return; return;
} }
@ -114,7 +178,7 @@ public class SqlInjectionUtil {
* sql * sql
* @param values * @param values
*/ */
public static void filterContent(String[] values) { public static void filterContent(String... values) {
filterContent(values, null); filterContent(values, null);
} }
@ -141,7 +205,7 @@ public class SqlInjectionUtil {
if (value.indexOf(xssArr[i]) > -1) { if (value.indexOf(xssArr[i]) > -1) {
log.error("请注意存在SQL注入关键词---> {}", xssArr[i]); log.error("请注意存在SQL注入关键词---> {}", xssArr[i]);
log.error("请注意值可能存在SQL注入风险!---> {}", value); log.error("请注意值可能存在SQL注入风险!---> {}", value);
throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value); throw new JeecgSqlInjectionException("请注意值可能存在SQL注入风险!--->" + value);
} }
} }
//update-begin-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的还需要额外的校验比如 单引号 //update-begin-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的还需要额外的校验比如 单引号
@ -151,13 +215,13 @@ public class SqlInjectionUtil {
if (value.indexOf(xssArr2[i]) > -1) { if (value.indexOf(xssArr2[i]) > -1) {
log.error("请注意存在SQL注入关键词---> {}", xssArr2[i]); log.error("请注意存在SQL注入关键词---> {}", xssArr2[i]);
log.error("请注意值可能存在SQL注入风险!---> {}", value); log.error("请注意值可能存在SQL注入风险!---> {}", value);
throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value); throw new JeecgSqlInjectionException("请注意值可能存在SQL注入风险!--->" + value);
} }
} }
} }
//update-end-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的还需要额外的校验比如 单引号 //update-end-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的还需要额外的校验比如 单引号
if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){ if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value); throw new JeecgSqlInjectionException("请注意值可能存在SQL注入风险!--->" + value);
} }
} }
return; return;
@ -188,11 +252,11 @@ public class SqlInjectionUtil {
if (value.indexOf(xssArr[i]) > -1 || value.startsWith(xssArr[i].trim())) { if (value.indexOf(xssArr[i]) > -1 || value.startsWith(xssArr[i].trim())) {
log.error("请注意存在SQL注入关键词---> {}", xssArr[i]); log.error("请注意存在SQL注入关键词---> {}", xssArr[i]);
log.error("请注意值可能存在SQL注入风险!---> {}", value); log.error("请注意值可能存在SQL注入风险!---> {}", value);
throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value); throw new JeecgSqlInjectionException("请注意值可能存在SQL注入风险!--->" + value);
} }
} }
if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){ if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value); throw new JeecgSqlInjectionException("请注意值可能存在SQL注入风险!--->" + value);
} }
return; return;
} }
@ -222,12 +286,12 @@ public class SqlInjectionUtil {
if (value.indexOf(xssArr[i]) > -1 || value.startsWith(xssArr[i].trim())) { if (value.indexOf(xssArr[i]) > -1 || value.startsWith(xssArr[i].trim())) {
log.error("请注意存在SQL注入关键词---> {}", xssArr[i]); log.error("请注意存在SQL注入关键词---> {}", xssArr[i]);
log.error("请注意值可能存在SQL注入风险!---> {}", value); log.error("请注意值可能存在SQL注入风险!---> {}", value);
throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value); throw new JeecgSqlInjectionException("请注意值可能存在SQL注入风险!--->" + value);
} }
} }
if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){ if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value); throw new JeecgSqlInjectionException("请注意值可能存在SQL注入风险!--->" + value);
} }
return; return;
} }
@ -285,7 +349,7 @@ public class SqlInjectionUtil {
if(matcher.find()){ if(matcher.find()){
String error = "请注意值可能存在SQL注入风险---> \\*.*\\"; String error = "请注意值可能存在SQL注入风险---> \\*.*\\";
log.error(error); log.error(error);
throw new RuntimeException(error); throw new JeecgSqlInjectionException(error);
} }
// issues/4737 sys/duplicate/check SQL注入 #4737 // issues/4737 sys/duplicate/check SQL注入 #4737
@ -293,7 +357,7 @@ public class SqlInjectionUtil {
if(sleepMatcher.find()){ if(sleepMatcher.find()){
String error = "请注意值可能存在SQL注入风险---> sleep"; String error = "请注意值可能存在SQL注入风险---> sleep";
log.error(error); log.error(error);
throw new RuntimeException(error); throw new JeecgSqlInjectionException(error);
} }
} }
} }

View File

@ -2,6 +2,7 @@ package org.jeecg.common.util.security;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.jeecg.common.exception.JeecgSqlInjectionException;
import java.util.*; import java.util.*;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -81,6 +82,12 @@ public abstract class AbstractQueryBlackListHandler {
} }
} }
// 返回黑名单校验结果(不合法直接抛出异常)
if(!flag){
log.error(this.getError());
throw new JeecgSqlInjectionException(this.getError());
}
return flag; return flag;
} }

View File

@ -1,24 +1,18 @@
package org.jeecg.modules.system.controller; package org.jeecg.modules.system.controller;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.common.util.SqlInjectionUtil;
import org.jeecg.modules.system.mapper.SysDictMapper;
import org.jeecg.modules.system.model.DuplicateCheckVo;
import org.jeecg.modules.system.security.DictQueryBlackListHandler;
import org.mybatis.spring.MyBatisSystemException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.system.model.DuplicateCheckVo;
import org.jeecg.modules.system.service.ISysDictService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/** /**
* @Title: DuplicateCheckAction * @Title: DuplicateCheckAction
@ -34,10 +28,7 @@ import lombok.extern.slf4j.Slf4j;
public class DuplicateCheckController { public class DuplicateCheckController {
@Autowired @Autowired
SysDictMapper sysDictMapper; ISysDictService sysDictService;
@Autowired
DictQueryBlackListHandler dictQueryBlackListHandler;
/** /**
* *
@ -47,14 +38,9 @@ public class DuplicateCheckController {
@RequestMapping(value = "/check", method = RequestMethod.GET) @RequestMapping(value = "/check", method = RequestMethod.GET)
@ApiOperation("重复校验接口") @ApiOperation("重复校验接口")
public Result<String> doDuplicateCheck(DuplicateCheckVo duplicateCheckVo, HttpServletRequest request) { public Result<String> doDuplicateCheck(DuplicateCheckVo duplicateCheckVo, HttpServletRequest request) {
Long num = null;
log.debug("----duplicate check------"+ duplicateCheckVo.toString()); log.debug("----duplicate check------"+ duplicateCheckVo.toString());
//关联表字典举例sys_user,realname,id
//SQL注入校验只限制非法串改数据库 // 1.填值为空,直接返回
final String[] sqlInjCheck = {duplicateCheckVo.getTableName(),duplicateCheckVo.getFieldName()};
SqlInjectionUtil.filterContent(sqlInjCheck);
// update-begin-author:taoyan date:20211227 for: JTC-25 【online报表】oracle 操作问题 录入弹框啥都不填直接保存 ①编码不是应该提示必填么?②报错也应该是具体文字提示,不是后台错误日志
if(StringUtils.isEmpty(duplicateCheckVo.getFieldVal())){ if(StringUtils.isEmpty(duplicateCheckVo.getFieldVal())){
Result rs = new Result(); Result rs = new Result();
rs.setCode(500); rs.setCode(500);
@ -62,31 +48,9 @@ public class DuplicateCheckController {
rs.setMessage("数据为空,不作处理!"); rs.setMessage("数据为空,不作处理!");
return rs; return rs;
} }
//update-begin-author:taoyan date:20220329 for: VUEN-223【安全漏洞】当前被攻击的接口
String checkSql = duplicateCheckVo.getTableName() + SymbolConstant.COMMA + duplicateCheckVo.getFieldName() + SymbolConstant.COMMA;
if(!dictQueryBlackListHandler.isPass(checkSql)){
return Result.error(dictQueryBlackListHandler.getError());
}
//update-end-author:taoyan date:20220329 for: VUEN-223【安全漏洞】当前被攻击的接口
// update-end-author:taoyan date:20211227 for: JTC-25 【online报表】oracle 操作问题 录入弹框啥都不填直接保存 ①编码不是应该提示必填么?②报错也应该是具体文字提示,不是后台错误日志
// update-begin-author:liusq date:20230721 for: [issues/5134] duplicate/check Sql泄露问题 // 2.返回结果
try{ if (sysDictService.duplicateCheckData(duplicateCheckVo)) {
if (StringUtils.isNotBlank(duplicateCheckVo.getDataId())) {
// [2].编辑页面校验
num = sysDictMapper.duplicateCheckCountSql(duplicateCheckVo);
} else {
// [1].添加页面校验
num = sysDictMapper.duplicateCheckCountSqlNoDataId(duplicateCheckVo);
}
}catch(MyBatisSystemException e){
log.error(e.getMessage(), e);
String errorCause = "查询异常,请检查唯一校验的配置!";
return Result.error(errorCause);
}
// update-end-author:liusq date:20230721 for: [issues/5134] duplicate/check Sql泄露问题
if (num == null || num == 0) {
// 该值可用 // 该值可用
return Result.ok("该值可用!"); return Result.ok("该值可用!");
} else { } else {
@ -96,20 +60,4 @@ public class DuplicateCheckController {
} }
} }
/**
* VUEN-2584issuesql
*
* @param e
* @return
*/
@ExceptionHandler(java.sql.SQLException.class)
public Result<?> handleSQLException(Exception e){
String msg = e.getMessage();
String extractvalue = "extractvalue";
String updatexml = "updatexml";
if(msg!=null && (msg.toLowerCase().indexOf(extractvalue)>=0 || msg.toLowerCase().indexOf(updatexml)>=0)){
return Result.error("校验失败sql解析异常");
}
return Result.error("校验失败sql解析异常" + msg);
}
} }

View File

@ -66,27 +66,6 @@ public interface SysDictMapper extends BaseMapper<SysDict> {
*/ */
public List<DictModelMany> queryDictItemsByCodeList(@Param("dictCodeList") List<String> dictCodeList); public List<DictModelMany> queryDictItemsByCodeList(@Param("dictCodeList") List<String> dictCodeList);
/**
* table text code
* @param table
* @param text
* @param code
* @return List<DictModel>
*/
@Deprecated
public List<DictModel> queryTableDictItemsByCode(@Param("table") String table,@Param("text") String text,@Param("code") String code);
/**
* table text code
* @param table
* @param text
* @param code
* @param filterSql
* @return List<DictModel>
*/
@Deprecated
public List<DictModel> queryTableDictItemsByCodeAndFilter(@Param("table") String table,@Param("text") String text,@Param("code") String code,@Param("filterSql") String filterSql);
/** /**
* table text code * table text code
* @param table * @param table
@ -114,40 +93,6 @@ public interface SysDictMapper extends BaseMapper<SysDict> {
*/ */
List<DictModelMany> queryManyDictByKeys(@Param("dictCodeList") List<String> dictCodeList, @Param("keys") List<String> keys); List<DictModelMany> queryManyDictByKeys(@Param("dictCodeList") List<String> dictCodeList, @Param("keys") List<String> keys);
/**
* table text code key
* @param table
* @param text
* @param code
* @param key
* @return String
*/
@Deprecated
public String queryTableDictTextByKey(@Param("table") String table,@Param("text") String text,@Param("code") String code,@Param("key") String key);
// /**
// * 通过查询指定table的 text code key 获取字典值,可批量查询
// *
// * @param table
// * @param text
// * @param code
// * @param keys
// * @return
// */
// @Deprecated
// List<DictModel> queryTableDictTextByKeys(@Param("table") String table, @Param("text") String text, @Param("code") String code, @Param("keys") List<String> keys);
// D /**
//// * 通过查询指定table的 text code key 获取字典值包含value
//// * @param table
//// * @param text
//// * @param code
//// * @param keyArray
//// * @return List<DictModel>
//// */
//// @Deprecated
//// public List<DictModel> queryTableictByKeys(@Param("table") String table, @Param("text") String text, @Param("code") String code, @Param("keyArray") String[] keyArray);
/** /**
* id -->value,departName -->text * id -->value,departName -->text
* @return * @return
@ -160,29 +105,6 @@ public interface SysDictMapper extends BaseMapper<SysDict> {
*/ */
public List<DictModel> queryAllUserBackDictModel(); public List<DictModel> queryAllUserBackDictModel();
// /**
// * 通过关键字查询出字典表
// * @param table
// * @param text
// * @param code
// * @param keyword
// * @return
// */
// @Deprecated
// public List<DictModel> queryTableDictItems(@Param("table") String table,@Param("text") String text,@Param("code") String code,@Param("keyword") String keyword);
// /**
// * 通过关键字查询出字典表
// * @param page
// * @param table
// * @param text
// * @param code
// * @param keyword
// * @return
// */
// //IPage<DictModel> queryTableDictItems(Page<DictModel> page, @Param("table") String table, @Param("text") String text, @Param("code") String code, @Param("keyword") String keyword);
/** /**
* *
* @param table * @param table
@ -240,7 +162,7 @@ public interface SysDictMapper extends BaseMapper<SysDict> {
* @return * @return
*/ */
@Deprecated @Deprecated
IPage<DictModel> queryTableDictWithFilter(Page<DictModel> page, @Param("table") String table, @Param("text") String text, @Param("code") String code, @Param("filterSql") String filterSql); IPage<DictModel> queryPageTableDictWithFilter(Page<DictModel> page, @Param("table") String table, @Param("text") String text, @Param("code") String code, @Param("filterSql") String filterSql);
/** /**
* *
@ -251,7 +173,7 @@ public interface SysDictMapper extends BaseMapper<SysDict> {
* @return * @return
*/ */
@Deprecated @Deprecated
List<DictModel> queryAllTableDictItems(@Param("table") String table, @Param("text") String text, @Param("code") String code, @Param("filterSql") String filterSql); List<DictModel> queryTableDictWithFilter(@Param("table") String table, @Param("text") String text, @Param("code") String code, @Param("filterSql") String filterSql);
/** /**
* *
@ -262,7 +184,9 @@ public interface SysDictMapper extends BaseMapper<SysDict> {
* @param codeValues in * @param codeValues in
* @return * @return
*/ */
List<DictModel> queryTableDictByKeysAndFilterSql(@Param("table") String table, @Param("text") String text, @Param("code") String code, @Param("filterSql") String filterSql, @Param("codeValues") List<String> codeValues); @Deprecated
List<DictModel> queryTableDictByKeysAndFilterSql(@Param("table") String table, @Param("text") String text, @Param("code") String code, @Param("filterSql") String filterSql,
@Param("codeValues") List<String> codeValues);
/** /**
* id * id

View File

@ -62,51 +62,6 @@
) )
</select> </select>
<!--通过查询指定table的 text code 获取字典-->
<select id="queryTableDictItemsByCode" parameterType="String" resultType="org.jeecg.common.system.vo.DictModel">
select ${text} as "text",${code} as "value" from ${table}
</select>
<!--通过查询指定table的 text code 获取字典(指定查询条件)-->
<select id="queryTableDictItemsByCodeAndFilter" parameterType="String" resultType="org.jeecg.common.system.vo.DictModel">
select ${text} as "text",${code} as "value" from ${table}
<if test="filterSql != null and filterSql != ''">
where ${filterSql}
</if>
</select>
<!--通过查询指定table的 text code key 获取字典值-->
<select id="queryTableDictTextByKey" parameterType="String" resultType="String">
select ${text} as "text" from ${table} where ${code}= #{key}
</select>
<!--通过查询指定table的 text code key 获取字典值,可批量查询
<select id="queryTableDictTextByKeys" parameterType="String" resultType="org.jeecg.common.system.vo.DictModel">
select ${text} as "text", ${code} as "value" from ${table} where ${code} IN (
<foreach item="key" collection="keys" separator=",">
#{key}
</foreach>
)
</select>-->
<!--通过查询指定table的 text code key 获取字典值包含value
<select id="queryTableDictByKeys" parameterType="String" resultType="org.jeecg.common.system.vo.DictModel">
select ${text} as "text", ${code} as "value" from ${table} where ${code} in
<foreach item="key" collection="keyArray" open="(" separator="," close=")">
#{key}
</foreach>
</select>-->
<!-- 重复校验 sql语句 -->
<select id="duplicateCheckCountSql" resultType="Long" parameterType="org.jeecg.modules.system.model.DuplicateCheckVo">
SELECT COUNT(*) FROM ${tableName} WHERE ${fieldName} = #{fieldVal} and id &lt;&gt; #{dataId}
</select>
<!-- 重复校验 sql语句 -->
<select id="duplicateCheckCountSqlNoDataId" resultType="Long" parameterType="org.jeecg.modules.system.model.DuplicateCheckVo">
SELECT COUNT(*) FROM ${tableName} WHERE ${fieldName} = #{fieldVal}
</select>
<!-- 查询部门信息 作为字典数据 --> <!-- 查询部门信息 作为字典数据 -->
<select id="queryAllDepartBackDictModel" resultType="org.jeecg.common.system.vo.DictModel"> <select id="queryAllDepartBackDictModel" resultType="org.jeecg.common.system.vo.DictModel">
select id as "value",depart_name as "text" from sys_depart where del_flag = '0' select id as "value",depart_name as "text" from sys_depart where del_flag = '0'
@ -117,16 +72,25 @@
select username as "value",realname as "text" from sys_user where del_flag = '0' select username as "value",realname as "text" from sys_user where del_flag = '0'
</select> </select>
<!--通过查询指定table的 text code 获取字典数据,且支持关键字查询
<select id="queryTableDictItems" parameterType="String" resultType="org.jeecg.common.system.vo.DictModel">
select ${text} as "text",${code} as "value" from ${table} where ${text} like #{keyword}
</select> -->
<!-- 根据表名、显示字段名、存储字段名、父ID查询树 --> <!-- *****************以下方法写法存在SQL注入风险***************** -->
<!-- 重复校验 sql语句【已加入SQL注入check】 -->
<sql id="checkDuplicateCountSqlFragment">
SELECT COUNT(1) FROM ${tableName} WHERE ${fieldName} = #{fieldVal}
</sql>
<select id="duplicateCheckCountSql" resultType="Long" parameterType="org.jeecg.modules.system.model.DuplicateCheckVo">
<include refid="checkDuplicateCountSqlFragment"></include>
AND id &lt;&gt; #{dataId}
</select>
<select id="duplicateCheckCountSqlNoDataId" resultType="Long" parameterType="org.jeecg.modules.system.model.DuplicateCheckVo">
<include refid="checkDuplicateCountSqlFragment"></include>
</select>
<!-- 根据表名、显示字段名、存储字段名、父ID查询树 【已加入SQL注入check】 -->
<select id="queryTreeList" parameterType="Object" resultType="org.jeecg.modules.system.model.TreeSelectModel"> <select id="queryTreeList" parameterType="Object" resultType="org.jeecg.modules.system.model.TreeSelectModel">
select ${text} as "title", select ${text} as "title",
${code} as "key", ${code} as "key",
<!-- udapte-begin-author:taoyan date:20211115 for: 自定义树控件只显示父节点,子节点无法展开 (此处还原不可再改) /issues/I4HZAL -->
<if test="hasChildField != null and hasChildField != ''"> <if test="hasChildField != null and hasChildField != ''">
<choose> <choose>
<when test="converIsLeafVal!=null and converIsLeafVal==1"> <when test="converIsLeafVal!=null and converIsLeafVal==1">
@ -137,11 +101,10 @@
</otherwise> </otherwise>
</choose> </choose>
</if> </if>
<!-- udapte-end-author:taoyan date:20211115 for: 自定义树控件只显示父节点,子节点无法展开 (此处还原不可再改) /issues/I4HZAL -->
${pidField} as parentId ${pidField} as parentId
from ${table} from ${table}
where where
<!-- udapte-begin-author:sunjianlei date:20220110 for: 【JTC-597】自定义树查询条件查不出数据 --> <!-- 父ID条件 -->
<if test="query == null"> <if test="query == null">
<choose> <choose>
<when test="pid != null and pid != ''"> <when test="pid != null and pid != ''">
@ -152,6 +115,7 @@
</otherwise> </otherwise>
</choose> </choose>
</if> </if>
<!-- 查询条件组装 -->
<if test="query!= null"> <if test="query!= null">
1 = 1 1 = 1
<foreach collection="query.entrySet()" item="value" index="key" > <foreach collection="query.entrySet()" item="value" index="key" >
@ -164,7 +128,7 @@
</otherwise> </otherwise>
</choose> </choose>
</foreach> </foreach>
<!-- udapte-end-author:sunjianlei date:20220615 for: 【issues/3709】自定义树查询条件没有处理父ID没有树状结构了 --> <!-- 【issues/3709】自定义树查询条件没有处理父ID没有树状结构了 -->
<choose> <choose>
<when test="pid != null and pid != ''"> <when test="pid != null and pid != ''">
and ${pidField} = #{pid} and ${pidField} = #{pid}
@ -173,45 +137,42 @@
and (${pidField} = '' OR ${pidField} IS NULL) and (${pidField} = '' OR ${pidField} IS NULL)
</otherwise> </otherwise>
</choose> </choose>
<!-- udapte-end-author:sunjianlei date:20220615 for: 【issues/3709】自定义树查询条件没有处理父ID没有树状结构了 -->
</if> </if>
<!-- udapte-end-author:sunjianlei date:20220110 for: 【JTC-597】自定义树查询条件查不出数据 -->
</select> </select>
<!-- 分页查询字典表数据支持text或code模糊查询匹配【已加入SQL注入check】 -->
<!-- 分页查询字典表数据 -->
<select id="queryDictTablePageList" parameterType="Object" resultType="org.jeecg.common.system.vo.DictModel"> <select id="queryDictTablePageList" parameterType="Object" resultType="org.jeecg.common.system.vo.DictModel">
select ${query.text} as "text", ${query.code} as "value" from ${query.table} select ${query.text} as "text", ${query.code} as "value" from ${query.table}
where 1 = 1 where
<if test="query.keyword != null and query.keyword != ''"> <if test="query.keyword != null and query.keyword != ''">
<bind name="bindKeyword" value="'%'+query.keyword+'%'"/> <bind name="bindKeyword" value="'%'+query.keyword+'%'"/>
and (${query.text} like #{bindKeyword} or ${query.code} like #{bindKeyword}) (${query.text} like #{bindKeyword} or ${query.code} like #{bindKeyword})
</if> </if>
<if test="query.codeValue != null and query.codeValue != ''"> <if test="query.codeValue != null and query.codeValue != ''">
and ${query.code} = #{query.codeValue} ${query.code} = #{query.codeValue}
</if> </if>
</select> </select>
<!--通过查询指定table的 text code 获取字典数据,且支持关键字和自定义查询条件查询 分页--> <!--查询表字典数据支持关键字和自定义查询条件【已加入SQL注入check】 -->
<sql id="queryTableDictWithFilterSqlFragment">
select ${text} as "text", ${code} as "value" from ${table}
<if test="filterSql != null and filterSql != ''">
where ${filterSql}
</if>
</sql>
<!--查询表字典数据,分页返回-->
<select id="queryPageTableDictWithFilter" parameterType="String" resultType="org.jeecg.common.system.vo.DictModel">
<include refid="queryTableDictWithFilterSqlFragment"></include>
</select>
<!--查询表字典数据,不分页返回-->
<select id="queryTableDictWithFilter" parameterType="String" resultType="org.jeecg.common.system.vo.DictModel"> <select id="queryTableDictWithFilter" parameterType="String" resultType="org.jeecg.common.system.vo.DictModel">
select ${text} as "text", ${code} as "value" from ${table} <include refid="queryTableDictWithFilterSqlFragment"></include>
<if test="filterSql != null and filterSql != ''">
${filterSql}
</if>
</select> </select>
<!--通过查询指定table的 text code 获取字典数据,且支持关键字和自定义查询条件查询 获取所有 --> <!-- 查询表字典的数据, 支持设置过滤条件和code值 精确匹配查询【已加入SQL注入check】 -->
<select id="queryAllTableDictItems" parameterType="String" resultType="org.jeecg.common.system.vo.DictModel">
select ${text} as "text", ${code} as "value" from ${table}
<if test="filterSql != null and filterSql != ''">
${filterSql}
</if>
</select>
<!-- 查询字典表的数据 支持设置过滤条件、设置存储值作为in查询条件 -->
<select id="queryTableDictByKeysAndFilterSql" parameterType="String" resultType="org.jeecg.common.system.vo.DictModel"> <select id="queryTableDictByKeysAndFilterSql" parameterType="String" resultType="org.jeecg.common.system.vo.DictModel">
select ${text} as "text", ${code} as "value" from ${table} where ${code} IN ( select ${text} as "text", ${code} as "value" from ${table}
where ${code} IN (
<foreach item="key" collection="codeValues" separator=","> <foreach item="key" collection="codeValues" separator=",">
#{key} #{key}
</foreach> </foreach>
@ -221,6 +182,8 @@
</if> </if>
</select> </select>
<!-- *****************以上方法写法存在SQL注入风险***************** -->
<!--根据应用id获取字典列表和详情--> <!--根据应用id获取字典列表和详情-->
<select id="getDictListByLowAppId" resultType="org.jeecg.modules.system.entity.SysDict"> <select id="getDictListByLowAppId" resultType="org.jeecg.modules.system.entity.SysDict">
select id,dict_name,dict_code from sys_dict select id,dict_name,dict_code from sys_dict

View File

@ -5,6 +5,7 @@ import org.jeecg.common.system.vo.DictModel;
import org.jeecg.common.system.vo.DictQuery; import org.jeecg.common.system.vo.DictQuery;
import org.jeecg.modules.system.entity.SysDict; import org.jeecg.modules.system.entity.SysDict;
import org.jeecg.modules.system.entity.SysDictItem; import org.jeecg.modules.system.entity.SysDictItem;
import org.jeecg.modules.system.model.DuplicateCheckVo;
import org.jeecg.modules.system.model.TreeSelectModel; import org.jeecg.modules.system.model.TreeSelectModel;
import org.jeecg.modules.system.vo.lowapp.SysDictVo; import org.jeecg.modules.system.vo.lowapp.SysDictVo;
@ -21,6 +22,15 @@ import java.util.Map;
*/ */
public interface ISysDictService extends IService<SysDict> { public interface ISysDictService extends IService<SysDict> {
/**
*
*
* @param duplicateCheckVo
* @return
*/
@Deprecated
public boolean duplicateCheckData(DuplicateCheckVo duplicateCheckVo);
/** /**
* code * code
* @param code * @param code
@ -51,13 +61,13 @@ public interface ISysDictService extends IService<SysDict> {
/** /**
* table text code * table text code
* @param table * @param tableFilterSql
* @param text * @param text
* @param code * @param code
* @return * @return
*/ */
@Deprecated @Deprecated
List<DictModel> queryTableDictItemsByCode(String table, String text, String code); List<DictModel> queryTableDictItemsByCode(String tableFilterSql, String text, String code);
/** /**
* table text code * table text code

View File

@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.jeecg.common.config.TenantContext; import org.jeecg.common.config.TenantContext;
import org.jeecg.common.constant.CacheConstant; import org.jeecg.common.constant.CacheConstant;
import org.jeecg.common.constant.CommonConstant; import org.jeecg.common.constant.CommonConstant;
@ -13,7 +14,6 @@ import org.jeecg.common.constant.DataBaseConstant;
import org.jeecg.common.constant.SymbolConstant; import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.common.exception.JeecgBootException; import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.system.query.QueryGenerator; import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.system.util.ResourceUtil; import org.jeecg.common.system.util.ResourceUtil;
import org.jeecg.common.system.vo.DictModel; import org.jeecg.common.system.vo.DictModel;
import org.jeecg.common.system.vo.DictModelMany; import org.jeecg.common.system.vo.DictModelMany;
@ -25,14 +25,18 @@ import org.jeecg.modules.system.entity.SysDict;
import org.jeecg.modules.system.entity.SysDictItem; import org.jeecg.modules.system.entity.SysDictItem;
import org.jeecg.modules.system.mapper.SysDictItemMapper; import org.jeecg.modules.system.mapper.SysDictItemMapper;
import org.jeecg.modules.system.mapper.SysDictMapper; import org.jeecg.modules.system.mapper.SysDictMapper;
import org.jeecg.modules.system.model.DuplicateCheckVo;
import org.jeecg.modules.system.model.TreeSelectModel; import org.jeecg.modules.system.model.TreeSelectModel;
import org.jeecg.modules.system.security.DictQueryBlackListHandler;
import org.jeecg.modules.system.service.ISysDictService; import org.jeecg.modules.system.service.ISysDictService;
import org.jeecg.modules.system.vo.lowapp.SysDictVo; import org.jeecg.modules.system.vo.lowapp.SysDictVo;
import org.mybatis.spring.MyBatisSystemException;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -53,6 +57,53 @@ public class SysDictServiceImpl extends ServiceImpl<SysDictMapper, SysDict> impl
private SysDictMapper sysDictMapper; private SysDictMapper sysDictMapper;
@Autowired @Autowired
private SysDictItemMapper sysDictItemMapper; private SysDictItemMapper sysDictItemMapper;
@Autowired
private DictQueryBlackListHandler dictQueryBlackListHandler;
@Override
public boolean duplicateCheckData(DuplicateCheckVo duplicateCheckVo) {
Long count = null;
// 1.针对采用 ${}写法的表名和字段进行转义和check
String table = SqlInjectionUtil.getSqlInjectTableName(duplicateCheckVo.getTableName());
String fieldName = SqlInjectionUtil.getSqlInjectField(duplicateCheckVo.getFieldName());
duplicateCheckVo.setTableName(table);
duplicateCheckVo.setFieldName(fieldName);
// 2.SQL注入check只限制非法串改数据库
//关联表字典举例sys_user,realname,id
SqlInjectionUtil.filterContent(table, fieldName);
// 3.表字典黑名单check
String checkSql = table + SymbolConstant.COMMA + fieldName + SymbolConstant.COMMA;
dictQueryBlackListHandler.isPass(checkSql);
// 4.执行SQL 查询是否存在值
try{
if (StringUtils.isNotBlank(duplicateCheckVo.getDataId())) {
// [1].编辑页面校验
count = sysDictMapper.duplicateCheckCountSql(duplicateCheckVo);
} else {
// [2].添加页面校验
count = sysDictMapper.duplicateCheckCountSqlNoDataId(duplicateCheckVo);
}
}catch(MyBatisSystemException e){
log.error(e.getMessage(), e);
String errorCause = "查询异常,请检查唯一校验的配置!";
throw new JeecgBootException(errorCause);
}
// 4.返回结果
if (count == null || count == 0) {
// 该值可用
return true;
} else {
// 该值不可用
log.info("该值不可用,系统中已存在!");
return false;
}
}
/** /**
* code * code
@ -152,22 +203,69 @@ public class SysDictServiceImpl extends ServiceImpl<SysDictMapper, SysDict> impl
/** /**
* table text code * table text code
* dictTableCacheredis10 * dictTableCacheredis10
* @param table * @param tableFilterSql
* @param text * @param text
* @param code * @param code
* @return * @return
*/ */
@Override @Override
//@Cacheable(value = CacheConstant.SYS_DICT_TABLE_CACHE) @Deprecated
public List<DictModel> queryTableDictItemsByCode(String table, String text, String code) { public List<DictModel> queryTableDictItemsByCode(String tableFilterSql, String text, String code) {
log.debug("无缓存dictTableList的时候调用这里"); log.debug("无缓存dictTableList的时候调用这里");
return sysDictMapper.queryTableDictItemsByCode(table,text,code);
// 1.表字典黑名单check
String str = tableFilterSql+","+text+","+code;
if(!dictQueryBlackListHandler.isPass(str)){
log.error(dictQueryBlackListHandler.getError());
return null;
}
// 2.分割SQL获取表名和条件
String table = null;
String filterSql = null;
if(tableFilterSql.toLowerCase().indexOf(DataBaseConstant.SQL_WHERE)>0){
String[] arr = tableFilterSql.split(" (?i)where ");
table = arr[0];
filterSql = oConvertUtils.getString(arr[1], null);
}else{
table = tableFilterSql;
}
// 3.SQL注入check
SqlInjectionUtil.filterContent(table, text, code);
SqlInjectionUtil.specialFilterContentForDictSql(filterSql);
// 4.针对采用 ${}写法的表名和字段进行转义和check
table = SqlInjectionUtil.getSqlInjectTableName(table);
text = SqlInjectionUtil.getSqlInjectField(text);
code = SqlInjectionUtil.getSqlInjectField(code);
//return sysDictMapper.queryTableDictItemsByCode(tableFilterSql,text,code);
return sysDictMapper.queryTableDictWithFilter(table,text,code,filterSql);
} }
@Override @Override
public List<DictModel> queryTableDictItemsByCodeAndFilter(String table, String text, String code, String filterSql) { public List<DictModel> queryTableDictItemsByCodeAndFilter(String table, String text, String code, String filterSql) {
log.debug("无缓存dictTableList的时候调用这里"); log.debug("无缓存dictTableList的时候调用这里");
return sysDictMapper.queryTableDictItemsByCodeAndFilter(table,text,code,filterSql);
// 1.SQL注入校验只限制非法串改数据库
SqlInjectionUtil.specialFilterContentForDictSql(table);
SqlInjectionUtil.filterContent(text, code);
SqlInjectionUtil.specialFilterContentForDictSql(filterSql);
// 2.表字典黑名单 Check
String str = table+","+text+","+code;
if(!dictQueryBlackListHandler.isPass(str)){
log.error(dictQueryBlackListHandler.getError());
return null;
}
// 3.针对采用 ${}写法的表名和字段进行转义和check
table = SqlInjectionUtil.getSqlInjectTableName(table);
text = SqlInjectionUtil.getSqlInjectField(text);
code = SqlInjectionUtil.getSqlInjectField(code);
return sysDictMapper.queryTableDictWithFilter(table,text,code,filterSql);
} }
/** /**
@ -183,27 +281,70 @@ public class SysDictServiceImpl extends ServiceImpl<SysDictMapper, SysDict> impl
@Cacheable(value = CacheConstant.SYS_DICT_TABLE_CACHE, unless = "#result == null ") @Cacheable(value = CacheConstant.SYS_DICT_TABLE_CACHE, unless = "#result == null ")
public String queryTableDictTextByKey(String table,String text,String code, String key) { public String queryTableDictTextByKey(String table,String text,String code, String key) {
log.debug("无缓存dictTable的时候调用这里"); log.debug("无缓存dictTable的时候调用这里");
return sysDictMapper.queryTableDictTextByKey(table,text,code,key);
// 1.表字典黑名单check
String str = table+","+text+","+code;
if(!dictQueryBlackListHandler.isPass(str)){
log.error(dictQueryBlackListHandler.getError());
return null;
}
// 2.sql注入check
SqlInjectionUtil.filterContent(table, text, code, key);
// 3.针对采用 ${}写法的表名和字段进行转义和check
table = SqlInjectionUtil.getSqlInjectTableName(table);
text = SqlInjectionUtil.getSqlInjectField(text);
code = SqlInjectionUtil.getSqlInjectField(code);
List<DictModel> dictModeList = sysDictMapper.queryTableDictByKeysAndFilterSql(table, text, code, null, Arrays.asList(key));
if(CollectionUtils.isEmpty(dictModeList)){
return null;
}else{
return dictModeList.get(0).getText();
}
//此方法删除20230902
//return sysDictMapper.queryTableDictTextByKey(table,text,code,key);
} }
@Override @Override
public List<DictModel> queryTableDictTextByKeys(String table, String text, String code, List<String> keys) { public List<DictModel> queryTableDictTextByKeys(String table, String text, String code, List<String> codeValues) {
//update-begin-author:taoyan date:20220113 for: @dict注解支持 dicttable 设置where条件 // 1.表字典黑名单check
String str = table+","+text+","+code;
if(!dictQueryBlackListHandler.isPass(str)){
log.error(dictQueryBlackListHandler.getError());
return null;
}
// 2.分割SQL获取表名和条件
String filterSql = null; String filterSql = null;
if(table.toLowerCase().indexOf(DataBaseConstant.SQL_WHERE)>0){ if(table.toLowerCase().indexOf(DataBaseConstant.SQL_WHERE)>0){
String[] arr = table.split(" (?i)where "); String[] arr = table.split(" (?i)where ");
table = arr[0]; table = arr[0];
filterSql = arr[1]; filterSql = arr[1];
} }
String[] tableAndFields = new String[]{table, text, code};
SqlInjectionUtil.filterContent(tableAndFields); // 3.SQL注入check
SqlInjectionUtil.filterContent(table, text, code);
SqlInjectionUtil.specialFilterContentForDictSql(filterSql); SqlInjectionUtil.specialFilterContentForDictSql(filterSql);
return sysDictMapper.queryTableDictByKeysAndFilterSql(table, text, code, filterSql, keys);
// 4.针对采用 ${}写法的表名和字段进行转义和check
table = SqlInjectionUtil.getSqlInjectTableName(table);
text = SqlInjectionUtil.getSqlInjectField(text);
code = SqlInjectionUtil.getSqlInjectField(code);
return sysDictMapper.queryTableDictByKeysAndFilterSql(table, text, code, filterSql, codeValues);
//update-end-author:taoyan date:20220113 for: @dict注解支持 dicttable 设置where条件 //update-end-author:taoyan date:20220113 for: @dict注解支持 dicttable 设置where条件
} }
@Override @Override
public List<String> queryTableDictByKeys(String table, String text, String code, String keys) { public List<String> queryTableDictByKeys(String table, String text, String code, String keys) {
String str = table+","+text+","+code;
if(!dictQueryBlackListHandler.isPass(str)){
log.error(dictQueryBlackListHandler.getError());
return null;
}
return this.queryTableDictByKeys(table, text, code, keys, true); return this.queryTableDictByKeys(table, text, code, keys, true);
} }
@ -213,46 +354,56 @@ public class SysDictServiceImpl extends ServiceImpl<SysDictMapper, SysDict> impl
* @param table * @param table
* @param text * @param text
* @param code * @param code
* @param keys () * @param codeValuesStr ()
* @param delNotExist truefalsekeykey * @param delNotExist truefalsekeykey
* @return * @return
*/ */
@Override @Override
//update-begin--Author:lvdandan Date:20201204 forJT-36【online】树形列表bug修改后还是显示原来值 暂时去掉缓存 public List<String> queryTableDictByKeys(String table, String text, String code, String codeValuesStr, boolean delNotExist) {
//@Cacheable(value = CacheConstant.SYS_DICT_TABLE_BY_KEYS_CACHE) if(oConvertUtils.isEmpty(codeValuesStr)){
//update-end--Author:lvdandan Date:20201204 forJT-36【online】树形列表bug修改后还是显示原来值 暂时去掉缓存
public List<String> queryTableDictByKeys(String table, String text, String code, String keys, boolean delNotExist) {
if(oConvertUtils.isEmpty(keys)){
return null; return null;
} }
String[] keyArray = keys.split(",");
//update-begin-author:taoyan date:2022-4-24 for: 下拉搜索组件表单编辑页面回显下拉搜索的文本的时候因为表名后配置了条件导致sql执行失败 //1.分割sql获取表名 和 条件sql
String filterSql = null; String filterSql = null;
if(table.toLowerCase().indexOf("where")!=-1){ if(table.toLowerCase().indexOf("where")!=-1){
String[] arr = table.split(" (?i)where "); String[] arr = table.split(" (?i)where ");
table = arr[0]; table = arr[0];
filterSql = arr[1]; filterSql = arr[1];
} }
String[] tableAndFields = new String[]{table, text, code};
SqlInjectionUtil.filterContent(tableAndFields);
SqlInjectionUtil.specialFilterContentForDictSql(filterSql);
List<DictModel> dicts = sysDictMapper.queryTableDictByKeysAndFilterSql(table, text, code, filterSql, Arrays.asList(keyArray));
//update-end-author:taoyan date:2022-4-24 for: 下拉搜索组件表单编辑页面回显下拉搜索的文本的时候因为表名后配置了条件导致sql执行失败
List<String> texts = new ArrayList<>(dicts.size());
// update-begin--author:sunjianlei--date:20210514--for新增delNotExist参数设为false不删除数据库里不存在的key ---- // 2.SQL注入check
// 查询出来的顺序可能是乱的,需要排个序 SqlInjectionUtil.filterContent(table, text, code);
for (String key : keyArray) { SqlInjectionUtil.specialFilterContentForDictSql(filterSql);
List<DictModel> res = dicts.stream().filter(i -> key.equals(i.getValue())).collect(Collectors.toList());
// 3.表字典黑名单check
String str = table+","+text+","+code;
if(!dictQueryBlackListHandler.isPass(str)){
log.error(dictQueryBlackListHandler.getError());
return null;
}
// 4.针对采用 ${}写法的表名和字段进行转义和check
table = SqlInjectionUtil.getSqlInjectTableName(table);
text = SqlInjectionUtil.getSqlInjectField(text);
code = SqlInjectionUtil.getSqlInjectField(code);
//字典条件值
String[] codeValues = codeValuesStr.split(",");
// 5.查询字典数据
List<DictModel> dicts = sysDictMapper.queryTableDictByKeysAndFilterSql(SqlInjectionUtil.getSqlInjectTableName(table),
SqlInjectionUtil.getSqlInjectField(text), SqlInjectionUtil.getSqlInjectField(code), filterSql, Arrays.asList(codeValues));
List<String> texts = new ArrayList<>(dicts.size());
// 6.查询出来的顺序可能是乱的,需要排个序
for (String conditionalVal : codeValues) {
List<DictModel> res = dicts.stream().filter(i -> conditionalVal.equals(i.getValue())).collect(Collectors.toList());
if (res.size() > 0) { if (res.size() > 0) {
texts.add(res.get(0).getText()); texts.add(res.get(0).getText());
} else if (!delNotExist) { } else if (!delNotExist) {
texts.add(key); texts.add(conditionalVal);
} }
} }
// update-end--author:sunjianlei--date:20210514--for新增delNotExist参数设为false不删除数据库里不存在的key ----
return texts; return texts;
} }
@ -309,11 +460,17 @@ public class SysDictServiceImpl extends ServiceImpl<SysDictMapper, SysDict> impl
Page<DictModel> page = new Page<DictModel>(1, pageSize); Page<DictModel> page = new Page<DictModel>(1, pageSize);
page.setSearchCount(false); page.setSearchCount(false);
//【issues/3713】字典接口存在SQL注入风险 //为了防止sqljeecg提供了防注入的方法可以在拼接 SQL 语句时自动对参数进行转义避免SQL注入攻击
SqlInjectionUtil.specialFilterContentForDictSql(code); // 1. 针对采用 ${}写法的表名和字段进行转义和check
table = SqlInjectionUtil.getSqlInjectTableName(table);
text = SqlInjectionUtil.getSqlInjectField(text);
code = SqlInjectionUtil.getSqlInjectField(code);
// 2. 查询条件SQL (获取条件sql方法含sql注入校验)
String filterSql = getFilterSql(table, text, code, condition, keyword); String filterSql = getFilterSql(table, text, code, condition, keyword);
IPage<DictModel> pageList = baseMapper.queryTableDictWithFilter(page, table, text, code, filterSql);
// 3. 返回表字典数据
IPage<DictModel> pageList = baseMapper.queryPageTableDictWithFilter(page, table, text, code, filterSql);
return pageList.getRecords(); return pageList.getRecords();
} }
@ -326,14 +483,16 @@ public class SysDictServiceImpl extends ServiceImpl<SysDictMapper, SysDict> impl
* @return * @return
*/ */
private String getFilterSql(String table, String text, String code, String condition, String keyword){ private String getFilterSql(String table, String text, String code, String condition, String keyword){
String keywordSql = null, filterSql = "", sqlWhere = " where "; String filterSql = "";
// update-begin-author:sunjianlei date:20220112 for: 【JTC-631】判断如果 table 携带了 where 条件,那么就使用 and 查询,防止报错 String keywordSql = null;
String sqlWhere = "where ";
//【JTC-631】判断如果 table 携带了 where 条件,那么就使用 and 查询,防止报错
if (table.toLowerCase().contains(sqlWhere)) { if (table.toLowerCase().contains(sqlWhere)) {
sqlWhere = " and "; sqlWhere = " and ";
} }
// update-end-author:sunjianlei date:20220112 for: 【JTC-631】判断如果 table 携带了 where 条件,那么就使用 and 查询,防止报错
//update-begin-author:taoyan date:2022-8-15 for: 下拉搜索组件 支持传入排序信息 查询排序 // 下拉搜索组件 支持传入排序信息 查询排序
String orderField = "", orderType = ""; String orderField = "", orderType = "";
if (oConvertUtils.isNotEmpty(keyword)) { if (oConvertUtils.isNotEmpty(keyword)) {
// 关键字里面如果写入了 排序信息 xxxxx[orderby:create_time,desc] // 关键字里面如果写入了 排序信息 xxxxx[orderby:create_time,desc]
@ -358,7 +517,8 @@ public class SysDictServiceImpl extends ServiceImpl<SysDictMapper, SysDict> impl
} }
} }
} }
//update-end-author:taoyan date:2022-8-15 for: 下拉搜索组件 支持传入排序信息 查询排序
//下拉搜索组件 支持传入排序信息 查询排序
if(oConvertUtils.isNotEmpty(condition) && oConvertUtils.isNotEmpty(keywordSql)){ if(oConvertUtils.isNotEmpty(condition) && oConvertUtils.isNotEmpty(keywordSql)){
filterSql+= sqlWhere + condition + " and " + keywordSql; filterSql+= sqlWhere + condition + " and " + keywordSql;
}else if(oConvertUtils.isNotEmpty(condition)){ }else if(oConvertUtils.isNotEmpty(condition)){
@ -366,24 +526,68 @@ public class SysDictServiceImpl extends ServiceImpl<SysDictMapper, SysDict> impl
}else if(oConvertUtils.isNotEmpty(keywordSql)){ }else if(oConvertUtils.isNotEmpty(keywordSql)){
filterSql+= sqlWhere + keywordSql; filterSql+= sqlWhere + keywordSql;
} }
//update-begin-author:taoyan date:2022-8-15 for: 下拉搜索组件 支持传入排序信息 查询排序
// 增加排序逻辑 // 增加排序逻辑
if (oConvertUtils.isNotEmpty(orderField)) { if (oConvertUtils.isNotEmpty(orderField)) {
filterSql += " order by " + orderField + " " + orderType; filterSql += " order by " + orderField + " " + orderType;
} }
//update-end-author:taoyan date:2022-8-15 for: 下拉搜索组件 支持传入排序信息 查询排序
return filterSql; // result.1 返回条件SQL去掉 where 关键词)
final String wherePattern = "(?i)where "; // (?i) 表示不区分大小写
String filterSqlString = filterSql.trim().replaceAll(wherePattern, "");
// result.2 条件SQL进行漏洞 check
SqlInjectionUtil.specialFilterContentForDictSql(filterSqlString);
return filterSqlString;
} }
@Override @Override
public List<DictModel> queryAllTableDictItems(String table, String text, String code, String condition, String keyword) { public List<DictModel> queryAllTableDictItems(String table, String text, String code, String condition, String keyword) {
// 1.获取条件sql
String filterSql = getFilterSql(table, text, code, condition, keyword); String filterSql = getFilterSql(table, text, code, condition, keyword);
List<DictModel> ls = baseMapper.queryAllTableDictItems(table, text, code, filterSql);
// 为了防止sqljeecg提供了防注入的方法可以在拼接 SQL 语句时自动对参数进行转义避免SQL注入攻击
// 2.针对采用 ${}写法的表名和字段进行转义和check
table = SqlInjectionUtil.getSqlInjectTableName(table);
text = SqlInjectionUtil.getSqlInjectField(text);
code = SqlInjectionUtil.getSqlInjectField(code);
List<DictModel> ls = baseMapper.queryTableDictWithFilter(table, text, code, filterSql);
return ls; return ls;
} }
@Override @Override
public List<TreeSelectModel> queryTreeList(Map<String, String> query, String table, String text, String code, String pidField, String pid, String hasChildField, int converIsLeafVal) { public List<TreeSelectModel> queryTreeList(Map<String, String> query, String table, String text, String code, String pidField, String pid, String hasChildField, int converIsLeafVal) {
return baseMapper.queryTreeList(query, table, text, code, pidField, pid, hasChildField,converIsLeafVal); //为了防止sqljeecg提供了防注入的方法可以在拼接 SQL 语句时自动对参数进行转义避免SQL注入攻击
// 1.针对采用 ${}写法的表名和字段进行转义和check
table = SqlInjectionUtil.getSqlInjectTableName(table);
text = SqlInjectionUtil.getSqlInjectField(text);
code = SqlInjectionUtil.getSqlInjectField(code);
pidField = SqlInjectionUtil.getSqlInjectField(pidField);
hasChildField = SqlInjectionUtil.getSqlInjectField(hasChildField);
// 2.检测最终SQL是否存在SQL注入风险
String dictCode = table + "," + text + "," + code;
SqlInjectionUtil.filterContent(dictCode);
// 3.表字典SQL表名黑名单 Check
if(!dictQueryBlackListHandler.isPass(dictCode)){
log.error("Sql异常{}", dictQueryBlackListHandler.getError());
return null;
}
// 4.检测查询条件是否存在SQL注入
Map<String, String> queryParams = null;
if (query != null) {
queryParams = new HashMap<>(5);
for (Map.Entry<String, String> searchItem : query.entrySet()) {
String fieldName = searchItem.getKey();
queryParams.put(SqlInjectionUtil.getSqlInjectField(fieldName), searchItem.getValue());
}
}
return baseMapper.queryTreeList(queryParams, table, text, code, pidField, pid, hasChildField, converIsLeafVal);
} }
@Override @Override
@ -405,6 +609,26 @@ public class SysDictServiceImpl extends ServiceImpl<SysDictMapper, SysDict> impl
@Override @Override
public List<DictModel> queryDictTablePageList(DictQuery query, int pageSize, int pageNo) { public List<DictModel> queryDictTablePageList(DictQuery query, int pageSize, int pageNo) {
Page page = new Page(pageNo,pageSize,false); Page page = new Page(pageNo,pageSize,false);
//为了防止sqljeecg提供了防注入的方法可以在拼接 SQL 语句时自动对参数进行转义避免SQL注入攻击
// 1. 针对采用 ${}写法的表名和字段进行转义和check
String table = SqlInjectionUtil.getSqlInjectTableName(query.getTable());
String text = SqlInjectionUtil.getSqlInjectTableName(query.getText());
String code = SqlInjectionUtil.getSqlInjectTableName(query.getCode());
query.setCode(table);
query.setTable(text);
query.setText(code);
// 2.表字典黑名单check
String dictCode = table+","+text+","+code;
if(!dictQueryBlackListHandler.isPass(dictCode)){
log.error(dictQueryBlackListHandler.getError());
return null;
}
// 3.SQL注入check
SqlInjectionUtil.filterContent(dictCode);
Page<DictModel> pageList = baseMapper.queryDictTablePageList(page, query); Page<DictModel> pageList = baseMapper.queryDictTablePageList(page, query);
return pageList.getRecords(); return pageList.getRecords();
} }
@ -419,17 +643,8 @@ public class SysDictServiceImpl extends ServiceImpl<SysDictMapper, SysDict> impl
// 字典Code格式不正确 // 字典Code格式不正确
return null; return null;
} }
//SQL注入校验只限制非法串改数据库
//update-begin-author:taoyan date:2022-7-4 for: issues/I5BNY9 指定带过滤条件的字典table在生成代码后失效
// 表名后也有可能带条件and语句 不能走filterContent方法
SqlInjectionUtil.specialFilterContentForDictSql(params[0]);
final String[] sqlInjCheck = {params[1], params[2]};
//update-end-author:taoyan date:2022-7-4 for: issues/I5BNY9 指定带过滤条件的字典table在生成代码后失效
//【issues/3713】字典接口存在SQL注入风险
SqlInjectionUtil.filterContent(sqlInjCheck);
if (params.length == 4) { if (params.length == 4) {
// SQL注入校验查询条件SQL 特殊check此方法仅供此处使用
SqlInjectionUtil.specialFilterContentForDictSql(params[3]);
ls = this.queryTableDictItemsByCodeAndFilter(params[0], params[1], params[2], params[3]); ls = this.queryTableDictItemsByCodeAndFilter(params[0], params[1], params[2], params[3]);
} else if (params.length == 3) { } else if (params.length == 3) {
ls = this.queryTableDictItemsByCode(params[0], params[1], params[2]); ls = this.queryTableDictItemsByCode(params[0], params[1], params[2]);
@ -454,7 +669,13 @@ public class SysDictServiceImpl extends ServiceImpl<SysDictMapper, SysDict> impl
@Override @Override
public List<DictModel> loadDict(String dictCode, String keyword, Integer pageSize) { public List<DictModel> loadDict(String dictCode, String keyword, Integer pageSize) {
//【issues/3713】字典接口存在SQL注入风险 // 1.表字典黑名单check
if(!dictQueryBlackListHandler.isPass(dictCode)){
log.error(dictQueryBlackListHandler.getError());
return null;
}
// 2.字典SQL注入风险check
SqlInjectionUtil.specialFilterContentForDictSql(dictCode); SqlInjectionUtil.specialFilterContentForDictSql(dictCode);
if (dictCode.contains(SymbolConstant.COMMA)) { if (dictCode.contains(SymbolConstant.COMMA)) {