bug fix:解决sql注入的问题

main
liuwx_gitee 11 months ago
parent 45c5d27366
commit 9227f53e1a

@ -0,0 +1,119 @@
package org.jeecg.common.constant;
/**
* @Description:
* @author: wangshuai
* @date: 20220330 17:44
*/
public class SymbolConstant {
/**
*
*/
public static final String SPOT = ".";
/**
*
*/
public static final String DOUBLE_BACKSLASH = "\\";
/**
*
*/
public static final String COLON = ":";
/**
*
*/
public static final String COMMA = ",";
/**
* }
*/
public static final String LEFT_CURLY_BRACKET = "{";
/**
* }
*/
public static final String RIGHT_CURLY_BRACKET = "}";
/**
* #
*/
public static final String WELL_NUMBER = "#";
/**
*
*/
public static final String SINGLE_SLASH = "/";
/**
*
*/
public static final String DOUBLE_SLASH = "//";
/**
*
*/
public static final String EXCLAMATORY_MARK = "!";
/**
* 线
*/
public static final String UNDERLINE = "_";
/**
*
*/
public static final String SINGLE_QUOTATION_MARK = "'";
/**
*
*/
public static final String ASTERISK = "*";
/**
*
*/
public static final String PERCENT_SIGN = "%";
/**
* $
*/
public static final String DOLLAR = "$";
/**
* &
*/
public static final String AND = "&";
/**
* ../
*/
public static final String SPOT_SINGLE_SLASH = "../";
/**
* ..\\
*/
public static final String SPOT_DOUBLE_BACKSLASH = "..\\";
/**
* #{
*/
public static final String SYS_VAR_PREFIX = "#{";
/**
* {{
*/
public static final String DOUBLE_LEFT_CURLY_BRACKET = "{{";
/**
* [
*/
public static final String SQUARE_BRACKETS_LEFT = "[";
/**
* ]
*/
public static final String SQUARE_BRACKETS_RIGHT = "]";
}

@ -4,28 +4,48 @@ import cn.hutool.crypto.SecureUtil;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.exception.JeecgBootException;
import javax.servlet.http.HttpServletRequest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* sql
*
*
* @author zhoujf
*/
@Slf4j
public class SqlInjectionUtil {
private final static String xssStr = "and |extractvalue|updatexml|geohash|gtid_subset|gtid_subtract|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|or |+|user()";
/**
* user()
*/
private final static String REGULAR_EXPRE_USER = "user[\\s]*\\([\\s]*\\)";
/**正则 show tables*/
private final static String SHOW_TABLES = "show\\s+tables";
/**
* sleep
*/
private final static Pattern FUN_SLEEP = Pattern.compile("sleep\\([\\d\\.]*\\)");
/**
* sql
*/
private final static Pattern SQL_ANNOTATION = Pattern.compile("/\\*[\\s\\S]*\\*/");
/**
* sign SQL
* 线 20200501
*/
private final static String TABLE_DICT_SIGN_SALT = "20200501";
private final static String xssStr = "'|and |exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|or |+";
/*
* sign
* @param dictCode:
* @param sign:
* @param request:
* @Return: void
*/
* sign
* @param dictCode:
* @param sign:
* @param request:
* @Return: void
*/
public static void checkDictTableSign(String dictCode, String sign, HttpServletRequest request) {
//表字典SQL注入漏洞,签名校验
String accessToken = request.getHeader("X-Access-Token");
@ -41,7 +61,7 @@ public class SqlInjectionUtil {
/**
* sql
*
*
* @param value
* @return
*/
@ -64,7 +84,7 @@ public class SqlInjectionUtil {
/**
* sql
*
*
* @param values
* @return
*/
@ -112,11 +132,11 @@ public class SqlInjectionUtil {
}
/**
* @() OnlineSQL
* @param value
* @return
*/
/**
* @() OnlineSQL
* @param value
* @return
*/
@Deprecated
public static void specialFilterContentForOnlineReport(String value) {
String specialXssStr = " exec | insert | delete | update | drop | chr | mid | master | truncate | char | declare |";
@ -136,4 +156,59 @@ public class SqlInjectionUtil {
return;
}
/**
*
* SQL
*
* @param value
* @return
*/
//@Deprecated
public static void specialFilterContentForDictSql(String value) {
String specialXssStr = " exec |extractvalue|updatexml|geohash|gtid_subset|gtid_subtract| insert | select | delete | update | drop | count | chr | mid | master | truncate | char | declare |;|+|user()";
String[] xssArr = specialXssStr.split("\\|");
if (value == null || "".equals(value)) {
return;
}
// 校验sql注释 不允许有sql注释
checkSqlAnnotation(value);
// 统一转为小写
value = value.toLowerCase();
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
//value = value.replaceAll("/\\*.*\\*/","");
for (int i = 0; i < xssArr.length; i++) {
if (value.indexOf(xssArr[i]) > -1 || value.startsWith(xssArr[i].trim())) {
log.error("请注意存在SQL注入关键词---> {}", xssArr[i]);
log.error("请注意值可能存在SQL注入风险!---> {}", value);
throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value);
}
}
if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value);
}
return;
}
/**
* sql
* @return
*/
public static void checkSqlAnnotation(String str){
Matcher matcher = SQL_ANNOTATION.matcher(str);
if(matcher.find()){
String error = "请注意值可能存在SQL注入风险---> \\*.*\\";
log.error(error);
throw new RuntimeException(error);
}
// issues/4737 sys/duplicate/check SQL注入 #4737
Matcher sleepMatcher = FUN_SLEEP.matcher(str);
if(sleepMatcher.find()){
String error = "请注意值可能存在SQL注入风险---> sleep";
log.error(error);
throw new RuntimeException(error);
}
}
}

@ -0,0 +1,231 @@
package org.jeecg.common.util.security;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* /
* @Author taoYan
* @Date 2022/3/17 11:21
**/
@Slf4j
public abstract class AbstractQueryBlackListHandler {
/**
* key-
* value-
* --
* ruleMap.put("sys_user", "*")sys_user
* ruleMap.put("sys_user", "username,password")sys_userusernamepassword
*/
public static Map<String, String> ruleMap = new HashMap<>();
/**
*
*/
public static final Pattern ILLEGAL_NAME_REG = Pattern.compile("[-]{2,}");
static {
ruleMap.put("sys_user", "password,salt");
}
/**
* sql -
*
* @param sql
* @return
*/
protected abstract List<QueryTable> getQueryTableInfo(String sql);
/**
* sql true
* @param sql
* @return
*/
public boolean isPass(String sql) {
List<QueryTable> list = null;
//【jeecg-boot/issues/4040】在线报表不支持子查询解析报错 #4040
try {
list = this.getQueryTableInfo(sql.toLowerCase());
} catch (Exception e) {
log.warn("校验sql语句解析报错{}",e.getMessage());
}
if(list==null){
return true;
}
log.info("--获取sql信息--", list.toString());
boolean flag = checkTableAndFieldsName(list);
if(flag == false){
return false;
}
for (QueryTable table : list) {
String name = table.getName();
String fieldString = ruleMap.get(name);
// 有没有配置这张表
if (fieldString != null) {
if ("*".equals(fieldString) || table.isAll()) {
flag = false;
log.warn("sql黑名单校验表【"+name+"】禁止查询");
break;
} else if (table.existSameField(fieldString)) {
flag = false;
break;
}
}
}
return flag;
}
/**
* sql
* issues/4983 SQL Injection in 3.5.1 #4983
* @return
*/
private boolean checkTableAndFieldsName(List<QueryTable> list){
boolean flag = true;
for(QueryTable queryTable: list){
String tableName = queryTable.getName();
if(hasSpecialString(tableName)){
flag = false;
log.warn("sql黑名单校验表名【"+tableName+"】包含特殊字符");
break;
}
Set<String> fields = queryTable.getFields();
for(String name: fields){
if(hasSpecialString(name)){
flag = false;
log.warn("sql黑名单校验字段名【"+name+"】包含特殊字符");
break;
}
}
}
return flag;
}
/**
*
* @param name
* @return
*/
private boolean hasSpecialString(String name){
Matcher m = ILLEGAL_NAME_REG.matcher(name);
if (m.find()) {
return true;
}
return false;
}
/**
*
*/
protected class QueryTable {
//表名
private String name;
//表的别名
private String alias;
// 字段名集合
private Set<String> fields;
// 是否查询所有字段
private boolean all;
public QueryTable() {
}
public QueryTable(String name, String alias) {
this.name = name;
this.alias = alias;
this.all = false;
this.fields = new HashSet<>();
}
public void addField(String field) {
this.fields.add(field);
}
public String getName() {
return name;
}
public Set<String> getFields() {
return new HashSet<>(fields);
}
public void setName(String name) {
this.name = name;
}
public void setFields(Set<String> fields) {
this.fields = fields;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public boolean isAll() {
return all;
}
public void setAll(boolean all) {
this.all = all;
}
/**
*
*
* @param fieldString
* @return
*/
public boolean existSameField(String fieldString) {
String[] arr = fieldString.split(",");
for (String exp : fields) {
for (String config : arr) {
if (exp.equals(config)) {
// 非常明确的列直接比较
log.warn("sql黑名单校验表【"+name+"】中字段【"+config+"】禁止查询");
return true;
} else {
// 使用表达式的列 只能判读字符串包含了
String aliasColumn = config;
if (alias != null && alias.length() > 0) {
aliasColumn = alias + "." + config;
}
if (exp.indexOf(aliasColumn) > 0) {
log.warn("sql黑名单校验表【"+name+"】中字段【"+config+"】禁止查询");
return true;
}
}
}
}
return false;
}
@Override
public String toString() {
return "QueryTable{" +
"name='" + name + '\'' +
", alias='" + alias + '\'' +
", fields=" + fields +
", all=" + all +
'}';
}
}
public String getError(){
// TODO
return "系统设置了安全规则,敏感表和敏感字段禁止查询,联系管理员授权!";
}
}

@ -1,10 +1,13 @@
package org.jeecg.modules.api.controller;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.dto.message.*;
import org.jeecg.common.api.dto.OnlineAuthDTO;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.common.system.vo.*;
import org.jeecg.common.util.SqlInjectionUtil;
import org.jeecg.modules.system.security.DictQueryBlackListHandler;
import org.jeecg.modules.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@ -17,6 +20,7 @@ import java.util.Set;
/**
* system
*/
@Slf4j
@RestController
@RequestMapping("/sys/api")
public class SystemAPIController {
@ -26,6 +30,8 @@ public class SystemAPIController {
@Autowired
private ISysUserService sysUserService;
@Autowired
private DictQueryBlackListHandler dictQueryBlackListHandler;
/**
@ -167,6 +173,11 @@ public class SystemAPIController {
*/
@GetMapping("/queryTableDictItemsByCode")
List<DictModel> queryTableDictItemsByCode(@RequestParam("table") String table, @RequestParam("text") String text, @RequestParam("code") String code){
String str = table+","+text+","+code;
if(!dictQueryBlackListHandler.isPass(str)){
log.error(dictQueryBlackListHandler.getError());
return null;
}
return sysBaseAPI.queryTableDictItemsByCode(table, text, code);
}
@ -190,6 +201,14 @@ public class SystemAPIController {
*/
@GetMapping("/queryFilterTableDictInfo")
List<DictModel> queryFilterTableDictInfo(@RequestParam("table") String table, @RequestParam("text") String text, @RequestParam("code") String code, @RequestParam("filterSql") String filterSql){
String str = table+","+text+","+code;
if(!dictQueryBlackListHandler.isPass(str)){
log.error(dictQueryBlackListHandler.getError());
return null;
}
String[] arr = new String[]{table, text, code};
SqlInjectionUtil.filterContent(arr);
SqlInjectionUtil.specialFilterContentForDictSql(filterSql);
return sysBaseAPI.queryFilterTableDictInfo(table, text, code, filterSql);
}
@ -204,6 +223,11 @@ public class SystemAPIController {
@Deprecated
@GetMapping("/queryTableDictByKeys")
public List<String> queryTableDictByKeys(@RequestParam("table") String table, @RequestParam("text") String text, @RequestParam("code") String code, @RequestParam("keyArray") String[] keyArray){
String str = table+","+text+","+code;
if(!dictQueryBlackListHandler.isPass(str)){
log.error(dictQueryBlackListHandler.getError());
return null;
}
return sysBaseAPI.queryTableDictByKeys(table, text, code, keyArray);
}
@ -457,6 +481,13 @@ public class SystemAPIController {
*/
@GetMapping("/translateDictFromTable")
public String translateDictFromTable(@RequestParam("table") String table, @RequestParam("text") String text, @RequestParam("code") String code, @RequestParam("key") String key){
String str = table+","+text+","+code;
if(!dictQueryBlackListHandler.isPass(str)){
log.error(dictQueryBlackListHandler.getError());
return null;
}
String[] arr = new String[]{table, text, code, key};
SqlInjectionUtil.filterContent(arr);
return sysBaseAPI.translateDictFromTable(table, text, code, key);
}

@ -4,9 +4,11 @@ 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.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@ -31,6 +33,8 @@ public class DuplicateCheckController {
@Autowired
SysDictMapper sysDictMapper;
@Autowired
DictQueryBlackListHandler dictQueryBlackListHandler;
/**
*
@ -47,6 +51,18 @@ public class DuplicateCheckController {
//SQL注入校验只限制非法串改数据库
final String[] sqlInjCheck = {duplicateCheckVo.getTableName(),duplicateCheckVo.getFieldName()};
SqlInjectionUtil.filterContent(sqlInjCheck);
if(StringUtils.isEmpty(duplicateCheckVo.getFieldVal())){
Result rs = new Result();
rs.setCode(500);
rs.setSuccess(true);
rs.setMessage("数据为空,不作处理!");
return rs;
}
String checkSql = duplicateCheckVo.getTableName() + SymbolConstant.COMMA + duplicateCheckVo.getFieldName() + SymbolConstant.COMMA;
if(!dictQueryBlackListHandler.isPass(checkSql)){
return Result.error(dictQueryBlackListHandler.getError());
}
if (StringUtils.isNotBlank(duplicateCheckVo.getDataId())) {
// [2].编辑页面校验
num = sysDictMapper.duplicateCheckCountSql(duplicateCheckVo);

@ -23,6 +23,7 @@ import org.jeecg.modules.system.entity.SysDict;
import org.jeecg.modules.system.entity.SysDictItem;
import org.jeecg.modules.system.model.SysDictTree;
import org.jeecg.modules.system.model.TreeSelectModel;
import org.jeecg.modules.system.security.DictQueryBlackListHandler;
import org.jeecg.modules.system.service.ISysDictItemService;
import org.jeecg.modules.system.service.ISysDictService;
import org.jeecg.modules.system.vo.SysDictPage;
@ -64,6 +65,8 @@ public class SysDictController {
private ISysDictItemService sysDictItemService;
@Autowired
public RedisTemplate<String, Object> redisTemplate;
@Autowired
private DictQueryBlackListHandler dictQueryBlackListHandler;
@RequestMapping(value = "/list", method = RequestMethod.GET)
public Result<IPage<SysDict>> queryPageList(SysDict sysDict,@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
@ -140,6 +143,9 @@ public class SysDictController {
public Result<List<DictModel>> getDictItems(@PathVariable String dictCode, @RequestParam(value = "sign",required = false) String sign,HttpServletRequest request) {
log.info(" dictCode : "+ dictCode);
Result<List<DictModel>> result = new Result<List<DictModel>>();
if(!dictQueryBlackListHandler.isPass(dictCode)){
return result.error500(dictQueryBlackListHandler.getError());
}
try {
List<DictModel> ls = sysDictService.getDictItems(dictCode);
if (ls == null) {
@ -204,6 +210,9 @@ public class SysDictController {
@RequestParam(value = "pageSize", required = false) Integer pageSize) {
log.info(" 加载字典表数据,加载关键字: "+ keyword);
Result<List<DictModel>> result = new Result<List<DictModel>>();
if(!dictQueryBlackListHandler.isPass(dictCode)){
return result.error500(dictQueryBlackListHandler.getError());
}
try {
List<DictModel> ls = sysDictService.loadDict(dictCode, keyword, pageSize);
if (ls == null) {
@ -274,6 +283,9 @@ public class SysDictController {
@RequestMapping(value = "/loadDictItem/{dictCode}", method = RequestMethod.GET)
public Result<List<String>> loadDictItem(@PathVariable String dictCode,@RequestParam(name="key") String keys, @RequestParam(value = "sign",required = false) String sign,@RequestParam(value = "delNotExist",required = false,defaultValue = "true") boolean delNotExist,HttpServletRequest request) {
Result<List<String>> result = new Result<>();
if(!dictQueryBlackListHandler.isPass(dictCode)){
return result.error500(dictQueryBlackListHandler.getError());
}
try {
if(dictCode.indexOf(",")!=-1) {
String[] params = dictCode.split(",");
@ -318,6 +330,9 @@ public class SysDictController {
// SQL注入漏洞 sign签名校验(表名,label字段,val字段,条件)
String dictCode = tbname+","+text+","+code+","+condition;
SqlInjectionUtil.filterContent(dictCode);
if(!dictQueryBlackListHandler.isPass(dictCode)){
return result.error500(dictQueryBlackListHandler.getError());
}
List<TreeSelectModel> ls = sysDictService.queryTreeList(query,tbname, text, code, pidField, pid,hasChildField);
result.setSuccess(true);
result.setResult(ls);
@ -341,6 +356,9 @@ public class SysDictController {
// SQL注入漏洞 sign签名校验
String dictCode = query.getTable()+","+query.getText()+","+query.getCode();
SqlInjectionUtil.filterContent(dictCode);
if(!dictQueryBlackListHandler.isPass(dictCode)){
return res.error500(dictQueryBlackListHandler.getError());
}
List<DictModel> ls = this.sysDictService.queryDictTablePageList(query,pageSize,pageNo);
res.setResult(ls);
res.setSuccess(true);

@ -0,0 +1,59 @@
package org.jeecg.modules.system.security;
import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.common.util.security.AbstractQueryBlackListHandler;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* sql
* dictCodeString
* table,text,code
* table where xxx,text,code
* table,text,code, where xxx
*
* @Author taoYan
* @Date 2022/3/23 21:10
**/
@Component("dictQueryBlackListHandler")
public class DictQueryBlackListHandler extends AbstractQueryBlackListHandler {
@Override
protected List<QueryTable> getQueryTableInfo(String dictCodeString) {
if (dictCodeString != null && dictCodeString.indexOf(SymbolConstant.COMMA) > 0) {
String[] arr = dictCodeString.split(SymbolConstant.COMMA);
if (arr.length != 3 && arr.length != 4) {
return null;
}
String tableName = getTableName(arr[0]);
QueryTable table = new QueryTable(tableName, "");
// 无论什么场景 第二、三个元素一定是表的字段直接add
table.addField(arr[1].trim());
String filed = arr[2].trim();
if (oConvertUtils.isNotEmpty(filed)) {
table.addField(filed);
}
List<QueryTable> list = new ArrayList<>();
list.add(table);
return list;
}
return null;
}
/**
* wheretable name
*
* @param str
* @return
*/
private String getTableName(String str) {
String[] arr = str.split("\\s+(?i)where\\s+");
// sys_user , (sys_user), sys_user%20, %60sys_user%60 issues/4393
String reg = "\\s+|\\(|\\)|`";
return arr[0].replaceAll(reg, "");
}
}
Loading…
Cancel
Save