From 06dab595a5c70a7a4de665aeb35c2c22fda699c0 Mon Sep 17 00:00:00 2001 From: Parker Date: Fri, 9 Oct 2020 14:49:48 +0800 Subject: [PATCH] =?UTF-8?q?=E9=98=B2=E7=81=AB=E5=A2=99=20=E8=BF=87?= =?UTF-8?q?=E6=BB=A4=20XSS=20SQL=20=E6=94=BB=E5=87=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/opsli/core/waf/XssProperties.java | 89 +++++++ .../org/opsli/core/waf/filter/WafFilter.java | 65 ++++++ .../servlet/WafHttpServletRequestWrapper.java | 217 ++++++++++++++++++ .../org/opsli/core/waf/util/SQLFilterKit.java | 47 ++++ .../org/opsli/core/waf/util/XSSFilterKit.java | 81 +++++++ 5 files changed, 499 insertions(+) create mode 100644 opsli-base-support/opsli-core/src/main/java/org/opsli/core/waf/XssProperties.java create mode 100644 opsli-base-support/opsli-core/src/main/java/org/opsli/core/waf/filter/WafFilter.java create mode 100644 opsli-base-support/opsli-core/src/main/java/org/opsli/core/waf/servlet/WafHttpServletRequestWrapper.java create mode 100644 opsli-base-support/opsli-core/src/main/java/org/opsli/core/waf/util/SQLFilterKit.java create mode 100644 opsli-base-support/opsli-core/src/main/java/org/opsli/core/waf/util/XSSFilterKit.java diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/waf/XssProperties.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/waf/XssProperties.java new file mode 100644 index 0000000..d54f066 --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/waf/XssProperties.java @@ -0,0 +1,89 @@ +package org.opsli.core.waf; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.List; + +/** + * Xss过滤器配置属性 + * + * @author 毕子航 951755883@qq.com + * @date 2018/10/26 + */ +@ConfigurationProperties(prefix = XssProperties.XSS) +public class XssProperties { + public static final String XSS = "xss"; + + /** + * xss 是否生效 + */ + boolean enable = false; + /** + * sql 过滤 + */ + boolean sqlFilter = false; + + /** + * xss过滤器的名字 + */ + String name = "xssFilter"; + /** + * xss过滤器需要过滤的路径 + */ + String[] urlPatterns = {"/*"}; + + List urlExclusion; + + /** + * 过滤器的优先级,值越小优先级越高 + */ + int order = 0; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String[] getUrlPatterns() { + return urlPatterns; + } + + public void setUrlPatterns(String[] urlPatterns) { + this.urlPatterns = urlPatterns; + } + + public List getUrlExclusion() { + return urlExclusion; + } + + public void setUrlExclusion(List urlExclusion) { + this.urlExclusion = urlExclusion; + } + + public int getOrder() { + return order; + } + + public void setOrder(int order) { + this.order = order; + } + + public boolean isEnable() { + return enable; + } + + public void setEnable(boolean enable) { + this.enable = enable; + } + + public boolean isSqlFilter() { + return sqlFilter; + } + + public void setSqlFilter(boolean sqlFilter) { + this.sqlFilter = sqlFilter; + } +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/waf/filter/WafFilter.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/waf/filter/WafFilter.java new file mode 100644 index 0000000..6faf7fe --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/waf/filter/WafFilter.java @@ -0,0 +1,65 @@ +package org.opsli.core.waf.filter; + + +import org.opsli.core.waf.servlet.WafHttpServletRequestWrapper; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.List; + +/** + * 防火墙 + * + * @author Parker + * @date 2020-10-09 + */ +public class WafFilter implements Filter { + + private boolean enableXssFilter = false; + private boolean enableSqlFilter = false; + + private List urlExclusion; + + + @Override + public void init(FilterConfig config) throws ServletException { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + HttpServletRequest httpServletRequest = (HttpServletRequest) request; + String servletPath = httpServletRequest.getServletPath(); + + // 如果是排除url 则放行 + if (urlExclusion != null && urlExclusion.contains(servletPath)) { + chain.doFilter(request, response); + } else { + // 执行过滤 + chain.doFilter( + new WafHttpServletRequestWrapper((HttpServletRequest) request, enableXssFilter, enableSqlFilter), + response); + } + } + + @Override + public void destroy() { + } + + // ============================ + + + public void setEnableXssFilter(boolean enableXssFilter) { + this.enableXssFilter = enableXssFilter; + } + + public void setEnableSqlFilter(boolean enableSqlFilter) { + this.enableSqlFilter = enableSqlFilter; + } + + public void setUrlExclusion(List urlExclusion) { + this.urlExclusion = urlExclusion; + } +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/waf/servlet/WafHttpServletRequestWrapper.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/waf/servlet/WafHttpServletRequestWrapper.java new file mode 100644 index 0000000..e00aee8 --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/waf/servlet/WafHttpServletRequestWrapper.java @@ -0,0 +1,217 @@ +package org.opsli.core.waf.servlet; + +import com.alibaba.fastjson.JSONObject; +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import org.opsli.common.constants.TokenConstants; +import org.opsli.common.exception.WafException; +import org.opsli.core.waf.util.XSSFilterKit; +import org.opsli.core.waf.util.SQLFilterKit; +import org.springframework.util.StreamUtils; +import org.springframework.util.StringUtils; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * 防火墙过滤处理器 + * + * @author Parker + * @date 2020-10-09 + */ +@Slf4j +public class WafHttpServletRequestWrapper extends HttpServletRequestWrapper { + + /** 头消息类型 */ + private static final String CONTENT_TYPE = "Content-Type"; + private static final List CONTENT_TYPE_LIST = Lists.newArrayList(); + + static { + CONTENT_TYPE_LIST.add("application/json"); + CONTENT_TYPE_LIST.add("application/json;charset=UTF-8"); + CONTENT_TYPE_LIST.add("application/x-www-form-urlencoded;charset=UTF-8"); + } + + /** + * 没被包装过的HttpServletRequest(特殊场景,需要自己过滤) + */ + HttpServletRequest orgRequest; + + + /** Xss 攻击防护 */ + private final boolean enableXssFilter; + /** SQL 攻击防护 */ + private final boolean enableSqlFilter; + + public WafHttpServletRequestWrapper(HttpServletRequest request, boolean enableXssFilter, boolean enableSqlFilter) { + super(request); + orgRequest = request; + this.enableXssFilter = enableXssFilter; + this.enableSqlFilter = enableSqlFilter; + } + + /** + * 过滤json数据 + * @return + * @throws IOException + */ + @Override + public ServletInputStream getInputStream() throws IOException { + + //非json类型,直接返回 + if (!CONTENT_TYPE_LIST.contains(super.getHeader(CONTENT_TYPE))) { + return super.getInputStream(); + } + + //为空,直接返回 + String json = StreamUtils.copyToString(super.getInputStream(), StandardCharsets.UTF_8); + if (StringUtils.isEmpty(json)) { + return super.getInputStream(); + } + + ByteArrayInputStream bis; + try { + // 防火墙过滤 + JSONObject jsonObject = JSONObject.parseObject(json); + Set keys = jsonObject.keySet(); + for (String key : keys) { + jsonObject.put(key, + filterParamString(String.valueOf(jsonObject.get(key))) + ); + } + json = jsonObject.toJSONString(); + }catch (WafException e){ + throw e; + }catch (Exception e){ + log.error(e.getMessage(),e); + }finally { + bis = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); + } + + return new ServletInputStream() { + @Override + public boolean isFinished() { + return true; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener readListener) { + } + + @Override + public int read() throws IOException { + return bis.read(); + } + }; + } + + + @Override + public String getParameter(String name) { + String value = super.getParameter(filterParamString(name)); + if (!StringUtils.isEmpty(value) && !TokenConstants.ACCESS_TOKEN.equals(name)) { + // 防火墙过滤 + value = filterParamString(value); + } + return value; + } + + @Override + public String[] getParameterValues(String name) { + String[] parameters = super.getParameterValues(name); + if (parameters == null || parameters.length == 0) { + return null; + } + + for (int i = 0; i < parameters.length; i++) { + if (!TokenConstants.ACCESS_TOKEN.equals(name)) { + if(parameters[i] != null){ + // 防火墙过滤 + parameters[i] = filterParamString(parameters[i]); + } + } + } + return parameters; + } + + @Override + public Map getParameterMap() { + Map map = new LinkedHashMap<>(); + Map parameters = super.getParameterMap(); + for (String key : parameters.keySet()) { + String[] values = parameters.get(key); + if (!TokenConstants.ACCESS_TOKEN.equals(key)) { + for (int i = 0; i < values.length; i++) { + if(values[i] != null){ + // 防火墙过滤 + values[i] = filterParamString(values[i]); + } + } + } + map.put(key, values); + } + return map; + } + + @Override + public String getHeader(String name) { + String value = super.getHeader(filterParamString(name)); + if (!StringUtils.isEmpty(value) && !TokenConstants.ACCESS_TOKEN.equals(name)) { + // 防火墙过滤 + value = filterParamString(value); + } + return value; + } + + + /** + * 获取最原始的request + */ + public HttpServletRequest getOrgRequest() { + return orgRequest; + } + + /** + * 获取最原始的request + */ + public static HttpServletRequest getOrgRequest(HttpServletRequest request) { + if (request instanceof WafHttpServletRequestWrapper) { + return ((WafHttpServletRequestWrapper) request).getOrgRequest(); + } + return request; + } + + /** + * @Description 过滤字符串内容 + * @param rawValue + * @return + */ + protected String filterParamString(String rawValue) { + if (StringUtils.isEmpty(rawValue)) { + return rawValue; + } + String tmpStr = rawValue; + if (this.enableXssFilter) { + tmpStr = XSSFilterKit.stripXSS(rawValue); + } + if (this.enableSqlFilter) { + tmpStr = XSSFilterKit.stripXSS( + SQLFilterKit.stripSQL(tmpStr)); + } + return tmpStr; + } +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/waf/util/SQLFilterKit.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/waf/util/SQLFilterKit.java new file mode 100644 index 0000000..2c364c0 --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/waf/util/SQLFilterKit.java @@ -0,0 +1,47 @@ +package org.opsli.core.waf.util; + +import org.opsli.common.exception.WafException; +import org.opsli.core.msg.CoreMsg; +import org.springframework.util.StringUtils; + +/** + * SQL过滤 + * + * @author Parker + * @date 2020-10-09 + */ +public final class SQLFilterKit { + + /** + * SQL注入过滤 + * + * @param str 待验证的字符串 + */ + public static String stripSQL(String str) { + if (StringUtils.isEmpty(str)) { + return null; + } + //去掉'|"|;|\字符 + str = StringUtils.replace(str, "'", ""); + str = StringUtils.replace(str, "\"", ""); + str = StringUtils.replace(str, ";", ""); + str = StringUtils.replace(str, "\\", ""); + + //转换成小写 + str = str.toLowerCase(); + + //非法字符 + String[] keywords = {"master", "truncate", "insert", "select", "delete", "update", "declare", "alter", "drop"}; + + //判断是否包含非法字符 + for (String keyword : keywords) { + if (str.contains(keyword)) { + throw new WafException(CoreMsg.WAF_EXCEPTION_SQL); + } + } + return str; + } + + // ==================== + private SQLFilterKit(){} +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/waf/util/XSSFilterKit.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/waf/util/XSSFilterKit.java new file mode 100644 index 0000000..33f41d2 --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/waf/util/XSSFilterKit.java @@ -0,0 +1,81 @@ +package org.opsli.core.waf.util; + +import org.apache.commons.lang3.StringUtils; + +import java.util.regex.Pattern; + +/** + * XSS 过滤 + * + * @author Parker + * @date 2020-10-09 + * */ +public final class XSSFilterKit { + + /** + * @Description 过滤XSS脚本内容 + * @param value + * @return + */ + public static String stripXSS(String value) { + String rlt = null; + if (StringUtils.isNotEmpty(value)) { + // NOTE: It's highly recommended to use the ESAPI library and uncomment the following line to + // avoid encoded attacks. + // value = ESAPI.encoder().canonicalize(value); + + // Avoid null characters + rlt = value.replaceAll("", ""); + + // Avoid anything between script tags + Pattern scriptPattern = Pattern.compile("", Pattern.CASE_INSENSITIVE); + rlt = scriptPattern.matcher(rlt).replaceAll(""); + + // Avoid anything in a src='...' type of expression + /*scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE + | Pattern.MULTILINE | Pattern.DOTALL); + rlt = scriptPattern.matcher(rlt).replaceAll(""); + + scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", Pattern.CASE_INSENSITIVE + | Pattern.MULTILINE | Pattern.DOTALL); + rlt = scriptPattern.matcher(rlt).replaceAll("");*/ + + // Remove any lonesome tag + scriptPattern = Pattern.compile("", Pattern.CASE_INSENSITIVE); + rlt = scriptPattern.matcher(rlt).replaceAll(""); + + // Remove any lonesome