Change the resolution of XSS: Remove the 'Filter + HttpServletRequestWrapper / ResponseBodyAdvise' method. Use owasp esapi to handle the invocation directly that has alerts.

pull/479/head
pandaapo 3 years ago
parent 58ae9a5f81
commit c028a2ba32

@ -33,6 +33,11 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.owasp.esapi</groupId>
<artifactId>esapi</artifactId>
</dependency>
</dependencies>
<build>

@ -17,6 +17,8 @@
package com.tencent.cloud.polaris.circuitbreaker.example;
import org.owasp.esapi.ESAPI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
@ -62,6 +64,12 @@ public class ServiceAController {
ResponseEntity<String> entity = restTemplate.getForEntity(
"http://polaris-circuitbreaker-example-b/example/service/b/info",
String.class);
return entity.getBody();
String response = entity.getBody();
return cleanXSS(response);
}
private String cleanXSS(String str) {
str = ESAPI.encoder().encodeForHTML(str);
return str;
}
}

@ -1,75 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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 com.tencent.cloud.polaris.circuitbreaker.example.xss;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang.StringEscapeUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
* Escape String in ResponseBody before write it into HttpResponse
*
* @author Daifu Wu
*/
@ControllerAdvice
public class XssResponseBodyAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter, Class aClass) {
return methodParameter.hasMethodAnnotation(ResponseBody.class) || methodParameter.getDeclaringClass().getAnnotation(ResponseBody.class) != null || methodParameter.getDeclaringClass().getAnnotation(RestController.class) != null;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
if (body instanceof String) {
body = StringEscapeUtils.escapeHtml((String) body);
return body;
}
try {
if (!((Class) body.getClass().getField("TYPE").get(null)).isPrimitive()) {
Map<String, Object> map = new HashMap<>();
Field[] fields = body.getClass().getDeclaredFields();
for (Field field: fields) {
field.setAccessible(true);
Object value = field.get(body);
if (value instanceof String) {
value = StringEscapeUtils.escapeHtml((String) value);
}
map.put(field.getName(), value);
}
return map;
}
}
catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
return body;
}
}

@ -0,0 +1,8 @@
ESAPI.printProperties=true
ESAPI.Encoder=org.owasp.esapi.reference.DefaultEncoder
# ESAPI Encoder
Encoder.AllowMultipleEncoding=false
Encoder.AllowMixedEncoding=false
Encoder.DefaultCodecList=HTMLEntityCodec,PercentCodec,JavaScriptCodec

@ -28,5 +28,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.owasp.esapi</groupId>
<artifactId>esapi</artifactId>
</dependency>
</dependencies>
</project>

@ -21,6 +21,7 @@ import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import com.tencent.cloud.common.constant.MetadataConstant;
import org.owasp.esapi.ESAPI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -67,7 +68,13 @@ public class GatewayCalleeController {
@RequestHeader(MetadataConstant.HeaderName.CUSTOM_METADATA) String metadataStr)
throws UnsupportedEncodingException {
LOG.info(URLDecoder.decode(metadataStr, UTF_8));
return URLDecoder.decode(metadataStr, UTF_8);
metadataStr = URLDecoder.decode(metadataStr, UTF_8);
return cleanXSS(metadataStr);
}
private String cleanXSS(String str) {
str = ESAPI.encoder().encodeForHTML(str);
return str;
}
}

@ -1,46 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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 com.tencent.cloud.polaris.gateway.example.callee.xss;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
/**
* filter request aim at defending against XSS
*
* @author Daifu Wu
*/
@WebFilter(urlPatterns = "/*", filterName = "xssFilter")
@Component
public class XssFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
filterChain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) servletRequest), servletResponse);
}
}

@ -1,193 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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 com.tencent.cloud.polaris.gateway.example.callee.xss;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang.StringEscapeUtils;
import org.springframework.web.servlet.HandlerMapping;
/**
* Wrap HttpServletRequest to escape String arguments
*
* @author Daifu Wu
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
private byte[] requestBody;
public XssHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
BufferedReader reader = request.getReader();
StringBuilder stringBuilder = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
if (stringBuilder.length() > 0) {
String json = stringBuilder.toString();
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> map = objectMapper.readValue(json, Map.class);
map.forEach((k, v) -> {
if (v instanceof String) {
v = cleanXSS((String) v);
map.put(k, v);
}
});
json = objectMapper.writeValueAsString(map);
requestBody = json.getBytes();
}
}
/**
* Handles arguments annotated by @RequestBody
*
* @return
*/
@Override
public ServletInputStream getInputStream() {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestBody);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
}
/**
* Handles arguments annotated by @RequestParam
*
* @param name string parameter
* @return
*/
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (values != null && values.length > 0) {
String[] safeValues = new String[values.length];
for (int i = 0; i < values.length; i++) {
safeValues[i] = cleanXSS(values[i]);
}
return safeValues;
}
return values;
}
/**
* Handles arguments annotated by @PathVariable
*
* @param name string parameter
* @return
*/
@Override
public Object getAttribute(String name) {
Object value = super.getAttribute(name);
if (name.equalsIgnoreCase(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE) && value != null && value instanceof Map) {
((Map) value).forEach((k, v) -> {
if (v instanceof String) {
v = cleanXSS((String) v);
((Map) value).put(k, v);
}
});
}
return value;
}
/**
* Handles arguments annotated by @RequestHeader
*
* @param name string parameter
* @return
*/
@Override
public Enumeration<String> getHeaders(String name) {
List<String> list = Collections.list(super.getHeaders(name));
list = list.stream().map((e) -> {
ObjectMapper objectMapper = new ObjectMapper();
try {
Map<String, String> map = objectMapper.readValue(e, Map.class);
map.forEach((k, v) -> {
v = cleanXSS(v);
map.put(k, v);
});
e = objectMapper.writeValueAsString(map);
}
catch (JsonProcessingException e1) {
e1.printStackTrace();
}
return e;
}).collect(Collectors.toList());
return Collections.enumeration(list);
}
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
if (value != null) {
value = cleanXSS(value);
}
return value;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
/**
* Escape string to defend against XSS
*
* @param value string request body
*/
private String cleanXSS(String value) {
value = StringEscapeUtils.escapeHtml(value);
return value;
}
}

@ -0,0 +1,8 @@
ESAPI.printProperties=true
ESAPI.Encoder=org.owasp.esapi.reference.DefaultEncoder
# ESAPI Encoder
Encoder.AllowMultipleEncoding=false
Encoder.AllowMixedEncoding=false
Encoder.DefaultCodecList=HTMLEntityCodec,PercentCodec,JavaScriptCodec

@ -27,5 +27,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.owasp.esapi</groupId>
<artifactId>esapi</artifactId>
</dependency>
</dependencies>
</project>

@ -21,6 +21,7 @@ import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import com.tencent.cloud.common.constant.MetadataConstant;
import org.owasp.esapi.ESAPI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -67,7 +68,13 @@ public class GatewayCalleeController {
@RequestHeader(MetadataConstant.HeaderName.CUSTOM_METADATA) String metadataStr)
throws UnsupportedEncodingException {
LOG.info(URLDecoder.decode(metadataStr, UTF_8));
return URLDecoder.decode(metadataStr, UTF_8);
metadataStr = URLDecoder.decode(metadataStr, UTF_8);
return cleanXSS(metadataStr);
}
private String cleanXSS(String str) {
str = ESAPI.encoder().encodeForHTML(str);
return str;
}
}

@ -1,46 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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 com.tencent.cloud.polaris.gateway.example.callee.xss;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
/**
* filter request aim at defending against XSS
*
* @author Daifu Wu
*/
@WebFilter(urlPatterns = "/*", filterName = "xssFilter")
@Component
public class XssFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
filterChain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) servletRequest), servletResponse);
}
}

@ -1,193 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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 com.tencent.cloud.polaris.gateway.example.callee.xss;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang.StringEscapeUtils;
import org.springframework.web.servlet.HandlerMapping;
/**
* Wrap HttpServletRequest to escape String arguments
*
* @author Daifu Wu
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
private byte[] requestBody;
public XssHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
BufferedReader reader = request.getReader();
StringBuilder stringBuilder = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
if (stringBuilder.length() > 0) {
String json = stringBuilder.toString();
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> map = objectMapper.readValue(json, Map.class);
map.forEach((k, v) -> {
if (v instanceof String) {
v = cleanXSS((String) v);
map.put(k, v);
}
});
json = objectMapper.writeValueAsString(map);
requestBody = json.getBytes();
}
}
/**
* Handles arguments annotated by @RequestBody
*
* @return ServletInputStream
*/
@Override
public ServletInputStream getInputStream() {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestBody);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
}
/**
* Handles arguments annotated by @RequestParam
*
* @param name string parameter
* @return
*/
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (values != null && values.length > 0) {
String[] safeValues = new String[values.length];
for (int i = 0; i < values.length; i++) {
safeValues[i] = cleanXSS(values[i]);
}
return safeValues;
}
return values;
}
/**
* Handles arguments annotated by @PathVariable
*
* @param name string parameter
* @return
*/
@Override
public Object getAttribute(String name) {
Object value = super.getAttribute(name);
if (name.equalsIgnoreCase(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE) && value != null && value instanceof Map) {
((Map) value).forEach((k, v) -> {
if (v instanceof String) {
v = cleanXSS((String) v);
((Map) value).put(k, v);
}
});
}
return value;
}
/**
* Handles arguments annotated by @RequestHeader
*
* @param name string parameter
* @return
*/
@Override
public Enumeration<String> getHeaders(String name) {
List<String> list = Collections.list(super.getHeaders(name));
list = list.stream().map((e) -> {
ObjectMapper objectMapper = new ObjectMapper();
try {
Map<String, String> map = objectMapper.readValue(e, Map.class);
map.forEach((k, v) -> {
v = cleanXSS(v);
map.put(k, v);
});
e = objectMapper.writeValueAsString(map);
}
catch (JsonProcessingException e1) {
e1.printStackTrace();
}
return e;
}).collect(Collectors.toList());
return Collections.enumeration(list);
}
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
if (value != null) {
value = cleanXSS(value);
}
return value;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
/**
* Escape string to defend against XSS
*
* @param value string request body
*/
private String cleanXSS(String value) {
value = StringEscapeUtils.escapeHtml(value);
return value;
}
}

@ -0,0 +1,8 @@
ESAPI.printProperties=true
ESAPI.Encoder=org.owasp.esapi.reference.DefaultEncoder
# ESAPI Encoder
Encoder.AllowMultipleEncoding=false
Encoder.AllowMixedEncoding=false
Encoder.DefaultCodecList=HTMLEntityCodec,PercentCodec,JavaScriptCodec

@ -17,6 +17,11 @@
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.owasp.esapi</groupId>
<artifactId>esapi</artifactId>
</dependency>
</dependencies>
<build>

@ -18,6 +18,7 @@
package com.tencent.cloud.polaris.router.example;
import org.owasp.esapi.ESAPI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -48,7 +49,15 @@ public class RouterCalleeController {
@PostMapping("/info")
public String info(String name, @RequestBody User user) {
LOG.info("Discovery Service Callee [{}] is called.", port);
return String.format("Discovery Service Callee [%s] is called. user = %s", port, user);
return String.format("Discovery Service Callee [%s] is called. user = %s", port, cleanXSS(user));
}
private User cleanXSS(User user) {
User u = new User();
String name = ESAPI.encoder().encodeForHTML(user.getName());
u.setName(name);
u.setAge(user.getAge());
return u;
}
}

@ -1,46 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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 com.tencent.cloud.polaris.router.example.xss;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
/**
* filter request aim at defending against XSS.
*
* @author Daifu Wu
*/
@WebFilter(urlPatterns = "/*", filterName = "xssFilter")
@Component
public class XssFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
filterChain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) servletRequest), servletResponse);
}
}

@ -1,159 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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 com.tencent.cloud.polaris.router.example.xss;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Map;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang.StringEscapeUtils;
import org.springframework.web.servlet.HandlerMapping;
/**
* Wrap HttpServletRequest to escape String arguments.
*
* @author Daifu Wu
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
private byte[] requestBody;
public XssHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
BufferedReader reader = request.getReader();
StringBuilder stringBuilder = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
String json = stringBuilder.toString();
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> map = objectMapper.readValue(json, Map.class);
map.forEach((k, v) -> {
if (v instanceof String) {
v = cleanXSS((String) v);
map.put(k, v);
}
});
json = objectMapper.writeValueAsString(map);
requestBody = json.getBytes();
}
/**
* Handles arguments annotated by @RequestBody.
*
* @return
*/
@Override
public ServletInputStream getInputStream() {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestBody);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
}
/**
* Handles arguments annotated by @RequestParam.
*
* @param name string parameter
* @return
*/
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (values != null && values.length > 0) {
String[] safeValues = new String[values.length];
for (int i = 0; i < values.length; i++) {
safeValues[i] = cleanXSS(values[i]);
}
return safeValues;
}
return values;
}
/**
* Handles arguments annotated by @PathVariable
*
* @param name string parameter
* @return
*/
@Override
public Object getAttribute(String name) {
Object value = super.getAttribute(name);
if (name.equalsIgnoreCase(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE) && value != null && value instanceof Map) {
((Map) value).forEach((k, v) -> {
if (v instanceof String) {
v = cleanXSS((String) v);
((Map) value).put(k, v);
}
});
}
return value;
}
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
if (value != null) {
value = cleanXSS(value);
}
return value;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
/**
* Escape string to defend against XSS
*
* @param value string request content
*/
private String cleanXSS(String value) {
value = StringEscapeUtils.escapeHtml(value);
return value;
}
}

@ -0,0 +1,8 @@
ESAPI.printProperties=true
ESAPI.Encoder=org.owasp.esapi.reference.DefaultEncoder
# ESAPI Encoder
Encoder.AllowMultipleEncoding=false
Encoder.AllowMixedEncoding=false
Encoder.DefaultCodecList=HTMLEntityCodec,PercentCodec,JavaScriptCodec

@ -17,6 +17,11 @@
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.owasp.esapi</groupId>
<artifactId>esapi</artifactId>
</dependency>
</dependencies>
<build>

@ -18,6 +18,7 @@
package com.tencent.cloud.polaris.router.example;
import org.owasp.esapi.ESAPI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -49,7 +50,15 @@ public class RouterCalleeController {
@PostMapping("/info")
public String info(@RequestParam("name") String name, @RequestBody User user) {
LOG.info("Discovery Service Callee [{}] is called.", port);
return String.format("Discovery Service Callee [%s] is called. user = %s", port, user);
return String.format("Discovery Service Callee [%s] is called. user = %s", port, cleanXSS(user));
}
private User cleanXSS(User user) {
User u = new User();
String name = ESAPI.encoder().encodeForHTML(user.getName());
u.setName(name);
u.setAge(user.getAge());
return u;
}
}

@ -1,46 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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 com.tencent.cloud.polaris.router.example.xss;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
/**
* filter request aim at defending against XSS
*
* @author Daifu Wu
*/
@WebFilter(urlPatterns = "/*", filterName = "xssFilter")
@Component
public class XssFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
filterChain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) servletRequest), servletResponse);
}
}

@ -1,159 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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 com.tencent.cloud.polaris.router.example.xss;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Map;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang.StringEscapeUtils;
import org.springframework.web.servlet.HandlerMapping;
/**
* Wrap HttpServletRequest to escape String arguments
*
* @author Daifu Wu
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
private byte[] requestBody;
public XssHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
BufferedReader reader = request.getReader();
StringBuilder stringBuilder = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
String json = stringBuilder.toString();
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> map = objectMapper.readValue(json, Map.class);
map.forEach((k, v) -> {
if (v instanceof String) {
v = cleanXSS((String) v);
map.put(k, v);
}
});
json = objectMapper.writeValueAsString(map);
requestBody = json.getBytes();
}
/**
* Handles arguments annotated by @RequestBody
*
* @return
*/
@Override
public ServletInputStream getInputStream() {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestBody);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
}
/**
* Handles arguments annotated by @RequestParam
*
* @param name string parameter
* @return
*/
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (values != null && values.length > 0) {
String[] safeValues = new String[values.length];
for (int i = 0; i < values.length; i++) {
safeValues[i] = cleanXSS(values[i]);
}
return safeValues;
}
return values;
}
/**
* Handles arguments annotated by @PathVariable
*
* @param name string parameter
* @return
*/
@Override
public Object getAttribute(String name) {
Object value = super.getAttribute(name);
if (name.equalsIgnoreCase(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE) && value != null && value instanceof Map) {
((Map) value).forEach((k, v) -> {
if (v instanceof String) {
v = cleanXSS((String) v);
((Map) value).put(k, v);
}
});
}
return value;
}
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
if (value != null) {
value = cleanXSS(value);
}
return value;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
/**
* Escape string to defend against XSS
*
* @param value string request body
*/
private String cleanXSS(String value) {
value = StringEscapeUtils.escapeHtml(value);
return value;
}
}

@ -0,0 +1,8 @@
ESAPI.printProperties=true
ESAPI.Encoder=org.owasp.esapi.reference.DefaultEncoder
# ESAPI Encoder
Encoder.AllowMultipleEncoding=false
Encoder.AllowMixedEncoding=false
Encoder.DefaultCodecList=HTMLEntityCodec,PercentCodec,JavaScriptCodec

@ -31,4 +31,14 @@
<maven.deploy.skip>true</maven.deploy.skip>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.owasp.esapi</groupId>
<artifactId>esapi</artifactId>
<version>2.1.0.1</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>

Loading…
Cancel
Save