Springboot过滤xss
两种xss类型:存储型xss、反射型xss。
简介:
存储型:持久化,代码是存储在服务器中的,如在个人信息或发表文章等地方,加入代码,如果没有过滤或过滤不严,那么这些代码将储存到服务器中,用户访问该页面的时候触发代码执行。这种XSS比较危险,容易造成蠕虫,盗窃cookie等
反射型:非持久化,需要欺骗用户自己去点击链接才能触发XSS代码(服务器中没有这样的页面和内容),一般容易出现在搜索页面。
问题产生:
公司代码发布前需要进行3部分安全扫描,其中第一个就出现这种问题了。如图。
解决方法:
首先springboot后台常见的接收参数一般有三种方式:@RequestParam 、 @PathVariable 、 @RequestBody
(1).过滤表单传值(?传参 即 @RequestParam)
step1:创建包装request的类 XssHttpServletRequestWrapper
package com.cpic.config.xss;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringEscapeUtils;
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;
import java.nio.charset.Charset;
//参考:https://www.freesion.com/article/7503775029/
//过滤表单传值(?传参 即 @RequestParam)
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String getQueryString() {
System.out.println("11===");
return StringEscapeUtils.escapeHtml4(super.getQueryString());
}
@Override
public String getParameter(String name) {
System.out.println("22===");
return StringEscapeUtils.escapeHtml4(super.getParameter(name));
}
@Override
public String[] getParameterValues(String name) {
//过滤表单传值(?传参 即 @RequestParam)
System.out.println("33===");
String[] values = super.getParameterValues(name);
if (ArrayUtils.isEmpty(values)) {
return values;
}
int length = values.length;
String[] escapeValues = new String[length];
for (int i = 0; i < length; i++) {
escapeValues[i] = StringEscapeUtils.escapeHtml4(values[i]);
}
return escapeValues;
}
//-------------------补充(考虑到部分参数会在header里面传过来)------------------
//参考:https://blog.csdn.net/weixin_38497019/article/details/95940285
@Override
public String getHeader(String name) {
String value = super.getHeader(name);
if (value == null)
return null;
return StringEscapeUtils.escapeHtml4(value);
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(inputHandlers(super.getInputStream ()).getBytes ());
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) { }
};
}
public String inputHandlers(ServletInputStream servletInputStream){
StringBuilder sb = new StringBuilder();
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(servletInputStream, Charset.forName("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 StringEscapeUtils.escapeHtml4(sb.toString ());
}
//如果不喜欢 使用StringEscapeUtils.escapeHtml4 的方法可以采取类似下面自定义的方法
/* private static String cleanXSS(String value) {
value = value.replaceAll("<", "<").replaceAll(">", ">");
value = value.replaceAll("%3C", "<").replaceAll("%3E", ">");
value = value.replaceAll("\\(", "(").replaceAll("\\)", ")");
value = value.replaceAll("%28", "(").replaceAll("%29", ")");
value = value.replaceAll("'", "'");
value = value.replaceAll("eval\\((.*)\\)", "");
value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
value = value.replaceAll("script", "");
return value;
}*/
}
step2:创建自定义过滤器XssFilter过滤请求
package com.cpic.config.xss;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
//参考:https://www.freesion.com/article/7503775029/
//过滤表单传值(?传参 即 @RequestParam)
@WebFilter(filterName="XSSFilter", urlPatterns="/*")
public class XssFilter implements Filter {
FilterConfig filterConfig = null;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("过滤器初始化");
this.filterConfig = filterConfig;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("执行过滤操作");
filterChain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) servletRequest), servletResponse);
}
@Override
public void destroy() {
System.out.println("过滤器销毁");
this.filterConfig = null;
}
}
step3:在springboot启动类添加过滤器扫描(@ServletComponentScan("com.cpic.config.xss")这个就是上面自定义过滤器的位置)
@EnableScheduling
@EnableAsync
@SpringBootApplication(exclude={RedisAutoConfiguration.class,RedisRepositoriesAutoConfiguration.class})
@ServletComponentScan("com.cpic.config.xss")
//过滤表单传值(?传参 即 @RequestParam)
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(Application.class);
springApplication.setBannerMode(Mode.OFF);
springApplication.run(args);
}
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
}
(2).过滤类似 @GetMapping("/testXss/{openId}")(url 后面/传参 即 @PathVariable)
step:创建类 XssHandlerMappingPostProcessor
package com.cpic.config.xss;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.util.HtmlUtils;
import org.springframework.web.util.UrlPathHelper;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
//https://blog.csdn.net/whatzhang007/article/details/111589693
//http://www.chaiguanxin.com/articles/2020/03/18/1584495603475.html
//过滤url后面的/传值(/传参 即 @PathVariable)
@Component
public class XssHandlerMappingPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException{
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException{
if(bean instanceof AbstractHandlerMapping){
AbstractHandlerMapping ahm = (AbstractHandlerMapping) bean;
ahm.setUrlPathHelper(new XssUrlPathHelper());
}
return bean;
}
static class XssUrlPathHelper extends UrlPathHelper {
@Override
public Map<String, String> decodePathVariables(HttpServletRequest request, Map<String, String> vars){
Map<String, String> result = super.decodePathVariables(request, vars);
if(!CollectionUtils.isEmpty(result)){
for(String key : result.keySet()){
result.put(key, cleanXSS(result.get(key)));
}
}
return result;
}
@Override
public MultiValueMap<String, String> decodeMatrixVariables(HttpServletRequest request,
MultiValueMap<String, String> vars){
MultiValueMap<String, String> mvm = super.decodeMatrixVariables(request, vars);
if(!CollectionUtils.isEmpty(mvm)){
for(String key : mvm.keySet()){
List<String> value = mvm.get(key);
for(int i = 0; i < value.size(); i++){
value.set(i, cleanXSS(value.get(i)));
}
}
}
return mvm;
}
private String cleanXSS(String value){
return HtmlUtils.htmlEscape(value);
}
}
}
(3).过滤json传值(json传参 即 @RequestBody)
step:创建类WebConfig
package com.cpic.config.webmvc;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.apache.commons.lang3.StringEscapeUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.io.IOException;
//https://www.freesion.com/article/7503775029/
//过滤post请求json传值(@RequestBody)
@Configuration
public class WebConfig implements WebMvcConfigurer, InitializingBean {
/**
* 默认就是@Autowired(required=true),表示注入的时候,该bean必须存在,否则就会注入失败required = false,表示忽略当前要注入的bean,如果有直接注入,没有跳过,不会报错
*/
@Autowired(required = false)
private ObjectMapper objectMapper;
private SimpleModule getSimpleModule() {
SimpleModule simpleModule = new SimpleModule();
simpleModule.addDeserializer(String.class, new JsonHtmlXssDeserializer(String.class));
return simpleModule;
}
/**
* 初始化bean的时候执行,可以针对某个具体的bean进行配置。afterPropertiesSet 必须实现 InitializingBean接口。实现 InitializingBean接口必须实现afterPropertiesSet方法
* 这个方法将在所有的属性被初始化后调用,但是会在init前调用
* @throws Exception
*/
@Override
public void afterPropertiesSet() throws Exception {
if (objectMapper != null) {
SimpleModule simpleModule = getSimpleModule();
objectMapper.registerModule(simpleModule);
}
}
}
/**
* 对入参的json进行转义
*/
class JsonHtmlXssDeserializer extends JsonDeserializer<String> {
public JsonHtmlXssDeserializer(Class<String> string) {
super();
}
@Override
public Class<String> handledType() {
return String.class;
}
@Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
String value = jsonParser.getValueAsString();
if (value != null) {
return StringEscapeUtils.escapeHtml4(value.toString());
}
return value;
}
}
(4).过滤?和/传值(即 @RequestParam和@PathVariable)
由于?和/实际上都是字符串接收参数,可以定义一个字符串转换器,让后配置到mvcConfig
step1:创建类EscapeStringConverter
package com.cpic.config.xss;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import org.springframework.web.util.HtmlUtils;
//http://www.10qianwan.com/articledetail/766291.html
@Component
public class EscapeStringConverter implements Converter<String, String> {
@Override
public String convert(String s) {
//同理可以和上面的一样自定义过滤转换规则
return StringUtils.isEmpty(s) ? s : HtmlUtils.htmlEscape(s);
}
}
step2:WebMvcConfig中添加如下方法
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new TokenDataHandlerMethodArgumentResolver());
}
测试
a).测试方法
//测试 @RequestBody 传参过滤(json传参)
@PostMapping("/testXss")
public BaseResponseResult test(@RequestBody User user){
System.out.println(user.getUserCode());
return new BaseResponseResult(200,"testXss",user.getUserCode());
}
//测试 @PathVariable 传参过滤(url后面带参数传参)
@GetMapping("/testXss/{openId}")
public BaseResponseResult getBasicUserInfo(@PathVariable(value="openId",required=true) String openId){
System.out.println(openId);
return new BaseResponseResult(200,"testXss",openId);
}
//测试 @RequestParam 传参过滤(?传参)
@GetMapping("/testXss1")
public BaseResponseResult test(@RequestParam(value="openId",required=true) String openId){
System.out.println(openId);
return new BaseResponseResult(200,"testXss",openId);
}
b).测试结果
补充:
a).sql注入常用语句:(参考:https://blog.csdn.net/u012610902/article/details/80994242)
<script>alert('hello,gaga!');</script> //经典语句,哈哈!
>"'><img src="javascript.:alert('XSS')">
>"'><script>alert('XSS')</script>
<table background='javascript.:alert(([code])'></table>
<object type=text/html data='javascript.:alert(([code]);'></object>
"+alert('XSS')+"
'><script>alert(document.cookie)</script>
='><script>alert(document.cookie)</script>
<script>alert(document.cookie)</script>
<script>alert(vulnerable)</script>
<script>alert('XSS')</script>
<img src="javascript:alert('XSS')">
b).过滤器白名单(富文本样式等)