「SpringCloud」(五十一) Gateway拦截器实现防止SQL注入/XSS攻击

  SQL注入是常见的系统安全问题之一,用户通过特定方式向系统发送SQL脚本,可直接自定义操作系统数据库,如果系统没有对SQL注入进行拦截,那么用户甚至可以直接对数据库进行增删改查等操作。

  XSS全称为Cross Site Script跨站点脚本攻击,和SQL注入类似,都是通过特定方式向系统发送攻击脚本,对系统进行控制和侵害。SQL注入主要以攻击数据库来达到攻击系统的目的,而XSS则是以恶意执行前端脚本来攻击系统。

  项目框架中使用mybatis/mybatis-plus数据持久层框架,在使用过程中,已有规避SQL注入的规则和使用方法。但是在实际开发过程中,由于各种原因,开发人员对持久层框架的掌握水平不同,有些特殊业务情况必须从前台传入SQL脚本。这时就需要对系统进行加固,防止特殊情况下引起的系统风险。

  在微服务架构下,我们考虑如何实现SQL注入/XSS攻击拦截时,肯定不会在每个微服务都实现一遍SQL注入/XSS攻击拦截。根据我们微服务系统的设计,所有的请求都会经过Gateway网关,所以在实现时就可以参照前面的日志拦截器来实现。在接收到一个请求时,通过拦截器解析请求参数,判断是否有SQL注入/XSS攻击参数,如果有,那么返回异常即可。

  我们前面在对微服务Gateway进行自定义扩展时,增加了Gateway插件功能。我们会根据系统需求开发各种Gateway功能扩展插件,并且可以根据系统配置文件来启用/禁用这些插件。下面我们就将防止SQL注入/XSS攻击拦截器作为一个Gateway插件来开发和配置。

1、新增SqlInjectionFilter 过滤器和XssInjectionFilter过滤器,分别用于解析请求参数并对参数进行判断是否存在SQL注入/XSS攻脚本。此处有公共判断方法,通过配置文件来读取请求的过滤配置,因为不是多有的请求都会引发SQL注入和XSS攻击,如果无差别的全部拦截和请求,那么势必影响到系统的性能。

/**
 * 防sql注入
 * @author GitEgg
 */
@Log4j2
@AllArgsConstructor
public class SqlInjectionFilter implements GlobalFilter, Ordered {

......

        // 当返回参数为true时,解析请求参数和返回参数
        if (shouldSqlInjection(exchange))
        {
            MultiValueMap 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注入
 * @author GitEgg
 */
@Log4j2
@AllArgsConstructor
public class XssInjectionFilter implements GlobalFilter, Ordered {

 ......

        // 当返回参数为true时,记录请求参数和返回参数
        if (shouldXssInjection(exchange))
        {
            MultiValueMap 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注入工具类
 * @author GitEgg
 */
@Slf4j
public class SqlInjectionRuleUtils {
    
    /**
     * SQL的正则表达式
     */
    private static String badStrReg = "b(and|or)b.{1,6}?(=|>|<|binb|blikeb)|/*.+?*/|<s*scriptb|bEXECb|UNION.+?SELECT|UPDATE.+?SET|INSERTs+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)s+(TABLE|DATABASE)";
    
    /**
     * SQL的正则表达式
     */
    private static Pattern sqlPattern = Pattern.compile(badStrReg, Pattern.CASE_INSENSITIVE);
    
    
    /**
     * sql注入校验 map
     *
     * @param map
     * @return
     */
    public static boolean mapRequestSqlKeyWordsCheck(MultiValueMap 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 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 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 ", Pattern.CASE_INSENSITIVE),
            // Avoid anything in a src="/a2020/img/data-img.jpg" data-src='...' type of expression
            Pattern.compile("src[r
]*=[r
]*\'(.*?)\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("src[r
]*=[r
]*"(.*?)"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            // Remove any lonesome  tag
            Pattern.compile("", Pattern.CASE_INSENSITIVE),
            // Avoid anything in a ", Pattern.CASE_INSENSITIVE),
            // Remove any lonesome 

	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
上滑加载更多 ↓
Top