Gateway拦截器实现预防SQL注入 XSS攻击
SQL注入是常见的系统安全问题之一,用户通过特定方式向系统发送SQL脚本,可直接自定义操作系统数据库,如果系统没有对SQL注入进行拦截,那么用户甚至可以直接对数据库进行增删改查等操作。 我们前面在对微服务Gateway进行自定义扩展时,增加了Gateway插件功能。我们会根据系统需求开发各种Gateway功能扩展插件,并且可以根据系统配置文件来启用/禁用这些插件。下面我们就将防止SQL注入/XSS攻击拦截器作为一个Gateway插件来开发和配置。 1、新增SqlInjectionFilter 过滤器和XssInjectionFilter过滤器,分别用于解析请求参数并对参数进行判断是否存在SQL注入/XSS攻脚本。此处有公共判断方法,通过配置文件来读取请求的过滤配置,因为不是多有的请求都会引发SQL注入和XSS攻击,如果无差别的全部拦截和请求,那么势必影响到系统的性能。 判断SQL注入的拦截器 复制 /** * 防sql注入 * @author GitEgg */ @Log4j2 @AllArgsConstructor public class SqlInjectionFilter implements GlobalFilter, Ordered { ...... // 当返回参数为true时,解析请求参数和返回参数 if (shouldSqlInjection(exchange)) { MultiValueMap<String, String> queryParams = request.getQueryParams(); boolean chkRetGetParams = SqlInjectionRuleUtils.mapRequestSqlKeyWordsCheck(queryParams); boolean chkRetJson = false; boolean chkRetFormData = false; HttpHeaders headers = request.getHeaders(); MediaType contentType = headers.getContentType(); long length = headers.getContentLength(); if(length > 0 && null != contentType && (contentType.includes(MediaType.APPLICATION_JSON) ||contentType.includes(MediaType.APPLICATION_JSON_UTF8))){ chkRetJson = SqlInjectionRuleUtils.jsonRequestSqlKeyWordsCheck(gatewayContext.getRequestBody()); } if(length > 0 && null != contentType && contentType.includes(MediaType.APPLICATION_FORM_URLENCODED)){ log.debug("[RequestLogFilter](Request)FormData:{}",gatewayContext.getFormData()); chkRetFormData = SqlInjectionRuleUtils.mapRequestSqlKeyWordsCheck(gatewayContext.getFormData()); } if (chkRetGetParams || chkRetJson || chkRetFormData) { return WebfluxResponseUtils.responseWrite(exchange, "参数中不允许存在sql关键字"); } return chain.filter(exchange); } else { return chain.filter(exchange); } } ...... } 判断XSS攻击的拦截器 复制 /** * 防xss注入 * @author GitEgg */ @Log4j2 @AllArgsConstructor public class XssInjectionFilter implements GlobalFilter, Ordered { ...... // 当返回参数为true时,记录请求参数和返回参数 if (shouldXssInjection(exchange)) { MultiValueMap<String, String> queryParams = request.getQueryParams(); boolean chkRetGetParams = XssInjectionRuleUtils.mapRequestSqlKeyWordsCheck(queryParams);
boolean chkRetJson = false; boolean chkRetFormData = false;
HttpHeaders headers = request.getHeaders(); MediaType contentType = headers.getContentType(); long length = headers.getContentLength(); if(length > 0 && null != contentType && (contentType.includes(MediaType.APPLICATION_JSON) ||contentType.includes(MediaType.APPLICATION_JSON_UTF8))){ chkRetJson = XssInjectionRuleUtils.jsonRequestSqlKeyWordsCheck(gatewayContext.getRequestBody()); } if(length > 0 && null != contentType && contentType.includes(MediaType.APPLICATION_FORM_URLENCODED)){ log.debug("[RequestLogFilter](Request)FormData:{}",gatewayContext.getFormData()); chkRetFormData = XssInjectionRuleUtils.mapRequestSqlKeyWordsCheck(gatewayContext.getFormData()); } if (chkRetGetParams || chkRetJson || chkRetFormData) { return WebfluxResponseUtils.responseWrite(exchange, "参数中不允许存在XSS注入关键字"); } return chain.filter(exchange); } else { return chain.filter(exchange); } } ...... 2、新增SqlInjectionRuleUtils工具类和XssInjectionRuleUtils工具类,通过正则表达式,用于判断参数是否属于SQL注入/XSS攻击脚本。 通过正则表达式对参数进行是否有SQL注入风险的判断 复制 /** * 防sql注入工具类 * @author GitEgg */ @Slf4j public class SqlInjectionRuleUtils { /** * SQL的正则表达式 */ private static String badStrReg = "\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)"; /** * sql注入校验 map * * @param map * @return */ public static boolean mapRequestSqlKeyWordsCheck(MultiValueMap<String, String> map) { //对post请求参数值进行sql注入检验 return map.entrySet().stream().parallel().anyMatch(entry -> { //这里需要将参数转换为小写来处理 String lowerValue = Optional.ofNullable(entry.getValue()) .map(Object::toString) .map(String::toLowerCase) .orElse(""); if (sqlPattern.matcher(lowerValue).find()) { log.error("参数[{}]中包含不允许sql的关键词", lowerValue); return true; } return false; }); } /** * sql注入校验 json * * @param value * @return */ public static boolean jsonRequestSqlKeyWordsCheck(String value) { if (JSONUtil.isJsonObj(value)) { JSONObject json = JSONUtil.parseObj(value); Map<String, Object> map = json; //对post请求参数值进行sql注入检验 return map.entrySet().stream().parallel().anyMatch(entry -> { //这里需要将参数转换为小写来处理 String lowerValue = Optional.ofNullable(entry.getValue()) .map(Object::toString) .map(String::toLowerCase) .orElse(""); if (sqlPattern.matcher(lowerValue).find()) { log.error("参数[{}]中包含不允许sql的关键词", lowerValue); return true; } return false; }); } else { JSONArray json = JSONUtil.parseArray(value); List<Object> list = json; //对post请求参数值进行sql注入检验 return list.stream().parallel().anyMatch(obj -> { //这里需要将参数转换为小写来处理 String lowerValue = Optional.ofNullable(obj) .map(Object::toString) .map(String::toLowerCase) .orElse(""); if (sqlPattern.matcher(lowerValue).find()) { log.error("参数[{}]中包含不允许sql的关键词", lowerValue); return true; } return false; }); } } } 通过正则表达式对参数进行是否有XSS攻击风险的判断 复制 /** * XSS注入过滤工具类 * @author GitEgg */ public class XssInjectionRuleUtils { private static final Pattern[] PATTERNS = { // Avoid anything in a <script> type of expression Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE), // Avoid anything in a src='...' type of expression Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), // Remove any lonesome </script> tag Pattern.compile("</script>", Pattern.CASE_INSENSITIVE), // Avoid anything in a <iframe> type of expression Pattern.compile("<iframe>(.*?)</iframe>", Pattern.CASE_INSENSITIVE), // Remove any lonesome <script ...> tag Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), // Remove any lonesome <img ...> tag Pattern.compile("<img(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), // Avoid eval(...) expressions Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), // Avoid expression(...) expressions Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), // Avoid javascript:... expressions Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE), // Avoid vbscript:... expressions Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE), // Avoid onload= expressions Pattern.compile("on(load|error|mouseover|submit|reset|focus|click)(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL) }; public static String stripXSS(String value) { if (StringUtils.isEmpty(value)) { return value; } for (Pattern scriptPattern : PATTERNS) { value = scriptPattern.matcher(value).replaceAll(""); } return value; } public static boolean hasStripXSS(String value) { if (!StringUtils.isEmpty(value)) { for (Pattern scriptPattern : PATTERNS) { if (scriptPattern.matcher(value).find() == true) { return true; } } } return false; } /** * xss注入校验 map * * @param map * @return */ public static boolean mapRequestSqlKeyWordsCheck(MultiValueMap<String, String> map) { //对post请求参数值进行sql注入检验 return map.entrySet().stream().parallel().anyMatch(entry -> { //这里需要将参数转换为小写来处理 String lowerValue = Optional.ofNullable(entry.getValue()) .map(Object::toString) .map(String::toLowerCase) .orElse(""); if (hasStripXSS(lowerValue)) { return true; } return false; }); } /** * xss注入校验 json * * @param value * @return */ public static boolean jsonRequestSqlKeyWordsCheck(String value) { if (JSONUtil.isJsonObj(value)) { JSONObject json = JSONUtil.parseObj(value); Map<String, Object> map = json; //对post请求参数值进行sql注入检验 return map.entrySet().stream().parallel().anyMatch(entry -> { //这里需要将参数转换为小写来处理 String lowerValue = Optional.ofNullable(entry.getValue()) .map(Object::toString) .map(String::toLowerCase) .orElse(""); if (hasStripXSS(lowerValue)) { return true; } return false; }); } else { JSONArray json = JSONUtil.parseArray(value); List<Object> list = json; //对post请求参数值进行sql注入检验 return list.stream().parallel().anyMatch(obj -> { //这里需要将参数转换为小写来处理 String lowerValue = Optional.ofNullable(obj) .map(Object::toString) .map(String::toLowerCase) .orElse(""); if (hasStripXSS(lowerValue)) { return true; } return false; }); } } } 在GatewayRequestContextFilter 中新增判断那些请求需要解析参数。因为出于性能等方面的考虑,网关并不是对所有的参数都进行解析,只有在需要记录日志、防止SQL注入/XSS攻击时才会进行解析。 (编辑:银川站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |