Prechádzať zdrojové kódy

:zap: 优化Xss运行逻辑

smallchill 3 rokov pred
rodič
commit
c42b165f27

+ 12 - 16
blade-core-tool/src/main/java/org/springblade/core/tool/config/XssConfiguration.java → blade-core-tool/src/main/java/org/springblade/core/tool/config/RequestConfiguration.java

@@ -16,10 +16,9 @@
 package org.springblade.core.tool.config;
 
 import lombok.AllArgsConstructor;
-import org.springblade.core.tool.support.xss.XssFilter;
-import org.springblade.core.tool.support.xss.XssProperties;
-import org.springblade.core.tool.support.xss.XssUrlProperties;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springblade.core.tool.request.BladeRequestFilter;
+import org.springblade.core.tool.request.RequestProperties;
+import org.springblade.core.tool.request.XssProperties;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.boot.web.servlet.FilterRegistrationBean;
 import org.springframework.context.annotation.Bean;
@@ -29,31 +28,28 @@ import org.springframework.core.Ordered;
 import javax.servlet.DispatcherType;
 
 /**
- * Xss配置类
+ * 过滤器配置类
  *
  * @author Chill
  */
 @Configuration(proxyBeanMethods = false)
 @AllArgsConstructor
-@ConditionalOnProperty(value = "blade.xss.enabled", havingValue = "true")
-@EnableConfigurationProperties({XssProperties.class, XssUrlProperties.class})
-public class XssConfiguration {
+@EnableConfigurationProperties({RequestProperties.class, XssProperties.class})
+public class RequestConfiguration {
 
+	private final RequestProperties requestProperties;
 	private final XssProperties xssProperties;
-	private final XssUrlProperties xssUrlProperties;
 
 	/**
-	 * 防XSS注入
-	 *
-	 * @return FilterRegistrationBean
+	 * 全局过滤器
 	 */
 	@Bean
-	public FilterRegistrationBean<XssFilter> xssFilterRegistration() {
-		FilterRegistrationBean<XssFilter> registration = new FilterRegistrationBean<>();
+	public FilterRegistrationBean<BladeRequestFilter> bladeFilterRegistration() {
+		FilterRegistrationBean<BladeRequestFilter> registration = new FilterRegistrationBean<>();
 		registration.setDispatcherTypes(DispatcherType.REQUEST);
-		registration.setFilter(new XssFilter(xssProperties, xssUrlProperties));
+		registration.setFilter(new BladeRequestFilter(requestProperties, xssProperties));
 		registration.addUrlPatterns("/*");
-		registration.setName("xssFilter");
+		registration.setName("bladeRequestFilter");
 		registration.setOrder(Ordered.LOWEST_PRECEDENCE);
 		return registration;
 	}

+ 119 - 0
blade-core-tool/src/main/java/org/springblade/core/tool/request/BladeHttpServletRequestWrapper.java

@@ -0,0 +1,119 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ * <p>
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.gnu.org/licenses/lgpl.html
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.tool.request;
+
+import org.springblade.core.tool.utils.WebUtil;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+/**
+ * 全局Request包装
+ *
+ * @author Chill
+ */
+public class BladeHttpServletRequestWrapper extends HttpServletRequestWrapper {
+
+	/**
+	 * 没被包装过的HttpServletRequest(特殊场景,需要自己过滤)
+	 */
+	private final HttpServletRequest orgRequest;
+	/**
+	 * 缓存报文,支持多次读取流
+	 */
+	private byte[] body;
+
+
+	public BladeHttpServletRequestWrapper(HttpServletRequest request) {
+		super(request);
+		orgRequest = request;
+	}
+
+	@Override
+	public BufferedReader getReader() throws IOException {
+		return new BufferedReader(new InputStreamReader(getInputStream()));
+	}
+
+	@Override
+	public ServletInputStream getInputStream() throws IOException {
+		if (super.getHeader(HttpHeaders.CONTENT_TYPE) == null) {
+			return super.getInputStream();
+		}
+
+		if (super.getHeader(HttpHeaders.CONTENT_TYPE).startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) {
+			return super.getInputStream();
+		}
+
+		if (body == null) {
+			body = WebUtil.getRequestBody(super.getInputStream()).getBytes();
+		}
+
+		final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
+
+		return new ServletInputStream() {
+
+			@Override
+			public int read() {
+				return byteArrayInputStream.read();
+			}
+
+			@Override
+			public boolean isFinished() {
+				return false;
+			}
+
+			@Override
+			public boolean isReady() {
+				return false;
+			}
+
+			@Override
+			public void setReadListener(ReadListener readListener) {
+			}
+		};
+	}
+
+	/**
+	 * 获取初始request
+	 *
+	 * @return HttpServletRequest
+	 */
+	public HttpServletRequest getOrgRequest() {
+		return orgRequest;
+	}
+
+	/**
+	 * 获取初始request
+	 *
+	 * @param request request
+	 * @return HttpServletRequest
+	 */
+	public static HttpServletRequest getOrgRequest(HttpServletRequest request) {
+		if (request instanceof BladeHttpServletRequestWrapper) {
+			return ((BladeHttpServletRequestWrapper) request).getOrgRequest();
+		}
+		return request;
+	}
+
+}

+ 20 - 9
blade-core-tool/src/main/java/org/springblade/core/tool/support/xss/XssFilter.java → blade-core-tool/src/main/java/org/springblade/core/tool/request/BladeRequestFilter.java

@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.springblade.core.tool.support.xss;
+package org.springblade.core.tool.request;
 
 import lombok.AllArgsConstructor;
 import org.springframework.util.AntPathMatcher;
@@ -23,15 +23,15 @@ import javax.servlet.http.HttpServletRequest;
 import java.io.IOException;
 
 /**
- * XSS过滤
+ * Request全局过滤
  *
  * @author Chill
  */
 @AllArgsConstructor
-public class XssFilter implements Filter {
+public class BladeRequestFilter implements Filter {
 
+	private final RequestProperties requestProperties;
 	private final XssProperties xssProperties;
-	private final XssUrlProperties xssUrlProperties;
 	private final AntPathMatcher antPathMatcher = new AntPathMatcher();
 
 	@Override
@@ -42,17 +42,28 @@ public class XssFilter implements Filter {
 	@Override
 	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
 		String path = ((HttpServletRequest) request).getServletPath();
-		if (isSkip(path)) {
+		// 跳过 Request 包装
+		if (!requestProperties.getEnabled() || isRequestSkip(path)) {
 			chain.doFilter(request, response);
-		} else {
+		}
+		// 默认 Request 包装
+		else if (!xssProperties.getEnabled() || isXssSkip(path)) {
+			BladeHttpServletRequestWrapper bladeRequest = new BladeHttpServletRequestWrapper((HttpServletRequest) request);
+			chain.doFilter(bladeRequest, response);
+		}
+		// Xss Request 包装
+		else {
 			XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
 			chain.doFilter(xssRequest, response);
 		}
 	}
 
-	private boolean isSkip(String path) {
-		return (xssUrlProperties.getExcludePatterns().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path)))
-			|| (xssProperties.getSkipUrl().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path)));
+	private boolean isRequestSkip(String path) {
+		return requestProperties.getSkipUrl().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path));
+	}
+
+	private boolean isXssSkip(String path) {
+		return xssProperties.getSkipUrl().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path));
 	}
 
 	@Override

+ 13 - 5
blade-core-tool/src/main/java/org/springblade/core/tool/support/xss/XssUrlProperties.java → blade-core-tool/src/main/java/org/springblade/core/tool/request/RequestProperties.java

@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.springblade.core.tool.support.xss;
+package org.springblade.core.tool.request;
 
 import lombok.Data;
 import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -22,14 +22,22 @@ import java.util.ArrayList;
 import java.util.List;
 
 /**
- * Xss配置类
+ * Request配置类
  *
  * @author Chill
  */
 @Data
-@ConfigurationProperties("blade.xss.url")
-public class XssUrlProperties {
+@ConfigurationProperties("blade.request")
+public class RequestProperties {
 
-	private final List<String> excludePatterns = new ArrayList<>();
+	/**
+	 * 开启自定义request
+	 */
+	private Boolean enabled = true;
+
+	/**
+	 * 放行url
+	 */
+	private List<String> skipUrl = new ArrayList<>();
 
 }

+ 9 - 9
blade-core-tool/src/main/java/org/springblade/core/tool/support/xss/HtmlFilter.java → blade-core-tool/src/main/java/org/springblade/core/tool/request/XssHtmlFilter.java

@@ -1,4 +1,4 @@
-package org.springblade.core.tool.support.xss;
+package org.springblade.core.tool.request;
 
 import org.springblade.core.tool.utils.StringPool;
 
@@ -41,7 +41,7 @@ import java.util.regex.Pattern;
  * @author Cal Hendersen
  * @author Michael Semb Wever
  */
-public final class HtmlFilter {
+public final class XssHtmlFilter {
 
 	/**
 	 * regex flag union representing /si modifiers in php
@@ -128,7 +128,7 @@ public final class HtmlFilter {
 	/**
 	 * Default constructor.
 	 */
-	public HtmlFilter() {
+	public XssHtmlFilter() {
 		vAllowed = new HashMap<>();
 
 		final ArrayList<String> aAtts = new ArrayList<String>();
@@ -158,7 +158,7 @@ public final class HtmlFilter {
 		vAllowedEntities = new String[]{"amp", "gt", "lt", "quot"};
 		stripComment = true;
 		encodeQuotes = true;
-		alwaysMakeTags = true;
+		alwaysMakeTags = false;
 	}
 
 	/**
@@ -166,7 +166,7 @@ public final class HtmlFilter {
 	 *
 	 * @param debug turn debug on with a true argument
 	 */
-	public HtmlFilter(final boolean debug) {
+	public XssHtmlFilter(final boolean debug) {
 		this();
 		vDebug = debug;
 
@@ -177,7 +177,7 @@ public final class HtmlFilter {
 	 *
 	 * @param conf map containing configuration. keys match field names.
 	 */
-	public HtmlFilter(final Map<String, Object> conf) {
+	public XssHtmlFilter(final Map<String, Object> conf) {
 
 		assert conf.containsKey("vAllowed") : "configuration requires vAllowed";
 		assert conf.containsKey("vSelfClosingTags") : "configuration requires vSelfClosingTags";
@@ -243,8 +243,8 @@ public final class HtmlFilter {
 		s = escapeComments(s);
 		debug("     escapeComments: " + s);
 
-		s = balanceHTML(s);
-		debug("        balanceHTML: " + s);
+		s = balanceHtml(s);
+		debug("        balanceHtml: " + s);
 
 		s = checkTags(s);
 		debug("          checkTags: " + s);
@@ -279,7 +279,7 @@ public final class HtmlFilter {
 		return buf.toString();
 	}
 
-	private String balanceHTML(String s) {
+	private String balanceHtml(String s) {
 		if (alwaysMakeTags) {
 			//
 			// try and form html

+ 18 - 40
blade-core-tool/src/main/java/org/springblade/core/tool/support/xss/XssHttpServletRequestWrapper.java → blade-core-tool/src/main/java/org/springblade/core/tool/request/XssHttpServletRequestWrapper.java

@@ -13,9 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.springblade.core.tool.support.xss;
+package org.springblade.core.tool.request;
 
 import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.core.tool.utils.WebUtil;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
 
@@ -27,12 +28,11 @@ import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
 /**
- * XSS过滤处理
+ * XSS过滤
  *
  * @author Chill
  */
@@ -41,12 +41,15 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
 	/**
 	 * 没被包装过的HttpServletRequest(特殊场景,需要自己过滤)
 	 */
-	HttpServletRequest orgRequest;
-
+	private final HttpServletRequest orgRequest;
+	/**
+	 * 缓存报文,支持多次读取流
+	 */
+	private byte[] body;
 	/**
 	 * html过滤
 	 */
-	private final static HtmlFilter HTML_FILTER = new HtmlFilter();
+	private final static XssHtmlFilter HTML_FILTER = new XssHtmlFilter();
 
 	public XssHttpServletRequestWrapper(HttpServletRequest request) {
 		super(request);
@@ -60,7 +63,7 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
 
 	@Override
 	public ServletInputStream getInputStream() throws IOException {
-		if (null == super.getHeader(HttpHeaders.CONTENT_TYPE)) {
+		if (super.getHeader(HttpHeaders.CONTENT_TYPE) == null) {
 			return super.getInputStream();
 		}
 
@@ -68,7 +71,11 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
 			return super.getInputStream();
 		}
 
-		final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(inputHandlers(super.getInputStream()).getBytes());
+		if (body == null) {
+			body = xssEncode(WebUtil.getRequestBody(super.getInputStream())).getBytes();
+		}
+
+		final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
 
 		return new ServletInputStream() {
 
@@ -93,36 +100,6 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
 		};
 	}
 
-	private String inputHandlers(ServletInputStream servletInputStream) {
-		StringBuilder sb = new StringBuilder();
-		BufferedReader reader = null;
-		try {
-			reader = new BufferedReader(new InputStreamReader(servletInputStream, StandardCharsets.UTF_8));
-			String line;
-			while ((line = reader.readLine()) != null) {
-				sb.append(line);
-			}
-		} catch (IOException e) {
-			e.printStackTrace();
-		} finally {
-			if (servletInputStream != null) {
-				try {
-					servletInputStream.close();
-				} catch (IOException e) {
-					e.printStackTrace();
-				}
-			}
-			if (reader != null) {
-				try {
-					reader.close();
-				} catch (IOException e) {
-					e.printStackTrace();
-				}
-			}
-		}
-		return xssEncode(sb.toString());
-	}
-
 	@Override
 	public String getParameter(String name) {
 		String value = super.getParameter(xssEncode(name));
@@ -138,6 +115,7 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
 		if (parameters == null || parameters.length == 0) {
 			return null;
 		}
+
 		for (int i = 0; i < parameters.length; i++) {
 			parameters[i] = xssEncode(parameters[i]);
 		}
@@ -172,7 +150,7 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
 	}
 
 	/**
-	 * 获取最原始的request
+	 * 获取初始request
 	 *
 	 * @return HttpServletRequest
 	 */
@@ -181,7 +159,7 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
 	}
 
 	/**
-	 * 获取最原始的request
+	 * 获取初始request
 	 *
 	 * @param request request
 	 * @return HttpServletRequest

+ 1 - 1
blade-core-tool/src/main/java/org/springblade/core/tool/support/xss/XssProperties.java → blade-core-tool/src/main/java/org/springblade/core/tool/request/XssProperties.java

@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.springblade.core.tool.support.xss;
+package org.springblade.core.tool.request;
 
 import lombok.Data;
 import org.springframework.boot.context.properties.ConfigurationProperties;

+ 75 - 0
blade-core-tool/src/main/java/org/springblade/core/tool/utils/WebUtil.java

@@ -26,11 +26,15 @@ import org.springframework.web.context.request.RequestContextHolder;
 import org.springframework.web.context.request.ServletRequestAttributes;
 import org.springframework.web.method.HandlerMethod;
 
+import javax.servlet.ServletInputStream;
 import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import java.io.BufferedReader;
 import java.io.IOException;
+import java.io.InputStreamReader;
 import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
 import java.util.Enumeration;
 
 
@@ -268,6 +272,77 @@ public class WebUtil extends org.springframework.web.util.WebUtils {
 		return str.replaceAll("&amp;", "&");
 	}
 
+	/**
+	 * 获取 request 请求体
+	 *
+	 * @param servletInputStream servletInputStream
+	 * @return body
+	 */
+	public static String getRequestBody(ServletInputStream servletInputStream) {
+		StringBuilder sb = new StringBuilder();
+		BufferedReader reader = null;
+		try {
+			reader = new BufferedReader(new InputStreamReader(servletInputStream, StandardCharsets.UTF_8));
+			String line;
+			while ((line = reader.readLine()) != null) {
+				sb.append(line);
+			}
+		} catch (IOException e) {
+			e.printStackTrace();
+		} finally {
+			if (servletInputStream != null) {
+				try {
+					servletInputStream.close();
+				} catch (IOException e) {
+					e.printStackTrace();
+				}
+			}
+			if (reader != null) {
+				try {
+					reader.close();
+				} catch (IOException e) {
+					e.printStackTrace();
+				}
+			}
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * 获取 request 请求内容
+	 *
+	 * @param request request
+	 * @return {String}
+	 */
+	public static String getRequestContent(HttpServletRequest request) {
+		try {
+			String queryString = request.getQueryString();
+			if (StringUtil.isNotBlank(queryString)) {
+				return new String(queryString.getBytes(Charsets.ISO_8859_1), Charsets.UTF_8).replaceAll("&amp;", "&").replaceAll("%22", "\"");
+			}
+			String charEncoding = request.getCharacterEncoding();
+			if (charEncoding == null) {
+				charEncoding = StringPool.UTF_8;
+			}
+			byte[] buffer = getRequestBody(request.getInputStream()).getBytes();
+			String str = new String(buffer, charEncoding).trim();
+			if (StringUtil.isBlank(str)) {
+				StringBuilder sb = new StringBuilder();
+				Enumeration<String> parameterNames = request.getParameterNames();
+				while (parameterNames.hasMoreElements()) {
+					String key = parameterNames.nextElement();
+					String value = request.getParameter(key);
+					StringUtil.appendBuilder(sb, key, "=", value, "&");
+				}
+				str = StringUtil.removeSuffix(sb.toString(), "&");
+			}
+			return str.replaceAll("&amp;", "&");
+		} catch (Exception ex) {
+			ex.printStackTrace();
+			return StringPool.EMPTY;
+		}
+	}
+
 
 }