Springboot 防止XSS攻击,包含解决RequestBody 的Json 格式参数

一、前言

最近项目做安全测试,发现存在XSS攻击的可能,于是乎上网找找看,找了很多基本都是继承HttpServletRequestWrapper,对getParam、getQueryString等获取参数的方法进行重写,对参数进行html转义,马上找一个加上试了试,可是发现保存的对象还是没有转义的,后来才想到项目是前后端分离,基本都是@RequestBody注解接收application/json格式参数,通过以上方法是获取不到参数的。

二、网上大多数的解决方案

XssHttpServletRequestWrapper、XssFilter

package com.sino.teamwork.common.extension;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.text.StringEscapeUtils;


public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {

    public XssHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
    }

    @Override
    public String getQueryString() {
        return StringEscapeUtils.escapeHtml4(super.getQueryString());
    }

    @Override
    public String getParameter(String name) {
        return StringEscapeUtils.escapeHtml4(super.getParameter(name));
    }

    @Override
    public String[] getParameterValues(String name) {
        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;
    }

}
package com.sino.teamwork.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
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;

import com.sino.teamwork.common.extension.XssHttpServletRequestWrapper;

/**
 * 
 * @ClassName: XssFilter
 * @Description:TODO(防止xss的过滤器)<br>
 * RequsestBody参数通过修改MappingJackson2HttpMessageConverter来过滤
 * @author: guomh
 * @date: 2020年4月07日 下午5:05:51
 * 
 * @Copyright: 2020
 *
 */
@WebFilter(filterName = "xssFilter", urlPatterns = "/*", asyncSupported = true)
@Component
public class XssFilter implements Filter {

    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // TODO Auto-generated method stub
        HttpServletRequest req = (HttpServletRequest) request;
        XssHttpServletRequestWrapper xssRequestWrapper = new XssHttpServletRequestWrapper(req);
        chain.doFilter(xssRequestWrapper, response);
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {
        // TODO Auto-generated method stub
    }

 
}

这个只能解决下表的前两种Content-Type方式传的参数

Content-Type 传参方式 接收方式
application/x-www-form-urlencoded 表单key-value HttpServletRequest Parameters 获取
multipart/form-data 表单key-value HttpServletRequest Parameters 获取
application/json json格式文本 HttpServletRequest IO流获取

三、RequestBody注解接收json格式参数解决方法

用@RequestBody 注解会使用默认转换器来进行转换,默认转换器初始化过程是这样的,springboot默认会用 MappingJackson2XmlHttpMessageConverter来转换json

看下官网的文档描述

An HttpMessageConverter implementation that can read and write JSON using Jackson’s ObjectMapper. JSON mapping can be customized as needed through the use of Jackson’s provided annotations. When further control is needed, a custom ObjectMapper can be injected through the ObjectMapper property for cases where custom JSON serializers/deserializers need to be provided for specific types. By default this converter supports ( application/json).

现在目标很明确了,就是要把默认的 MappingJackson2XmlHttpMessageConverter 给替换掉,我们自己写,然后在转换json参数后再进行html转义,理所当然的想到如下办法

@Bean
HttpMessageConverter<?> MappingJackson2HttpMessageConverter() {

}

我们看一下SpringMVC配置 WebMvcConfigurationSupport 里面获取 MessageConverter 的方法,如下图

里面有几个重要的方法,我们看下这几个方法的注释:

addDefaultHttpMessageConverters是系统默认的Converters,我们此方法最重要的一部分,MappingJackson2HttpMessageConverter是new出来的对象,所以并没有被spring容器管理,所以这也就说明了我们通过上面@Bean注解是无法替换掉系统默认的

configureMessageConverters 是自定义的MessageConverters,重写此方法,就是自己手动配置,不会采用springboot默认配置

extendMessageConverters的注释,我们看是扩展或修改converters的,因此我们也通过此方法也可以修改系统默认的

因此我们看到通过重写 configureMessageConverters 、extendMessageConverters 两个方法都可以修改系统默认的转换器

方法一:

重写 configureMessageConverters,我们需要把addDefaultHttpMessageConverters里面系统默认的转换器都写一遍,以保证其他的转化器有效,我们可以把 addDefaultHttpMessageConverters 源码复制出来,在 new MappingJackson2HttpMessageConverter 那里,我们可以 new 一个自定义的MappingJackson2HttpMessageConverter,但是我不建议用此方法,因为addDefaultHttpMessageConverters里面的内容很多,还有一些私有变量,复制出来有些不方便,还容易出错。

方法二:

重写extendMessageConverters,此方法注释说就是让来修改已经配置好的转化器列表呢,我们只需要遍历列表,找到MappingJackson2HttpMessageConverter,我们可以根据类型来判断哪个是 MappingJackson2HttpMessageConverter ,然后移除(注意遍历移除一定要用迭代器),把自定义的添加进去就好了,我们写在 WebMvcConfig 里


@Configuration
public class WebMvcConfig  extends WebMvcConfigurationSupport {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");

        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

    
    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
        /**
         * 替换默认的MappingJackson2HttpMessageConverter,过滤(json请求参数)xss
         */
    	ListIterator<HttpMessageConverter<?>> listIterator = messageConverters.listIterator();
    	while(listIterator.hasNext()) {
    		HttpMessageConverter<?> next = listIterator.next();
    		if(next instanceof MappingJackson2HttpMessageConverter) {
    			listIterator.remove();
    			break;
    		}
    	}
        messageConverters.add(getMappingJackson2HttpMessageConverter());

    }
    
    public MappingJackson2HttpMessageConverter getMappingJackson2HttpMessageConverter() {
        // 创建自定义ObjectMapper
        SimpleModule module = new SimpleModule();
        module.addDeserializer(String.class, new JsonHtmlXssDeserializer(String.class));
        ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().applicationContext(this.getApplicationContext()).build();
        objectMapper.registerModule(module);
        // 创建自定义消息转换器
        MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
        //设置中文编码格式
        List<MediaType> list = new ArrayList<>();
        list.add(MediaType.APPLICATION_JSON_UTF8);
        mappingJackson2HttpMessageConverter.setSupportedMediaTypes(list);
        return mappingJackson2HttpMessageConverter;
    }

}

/**
 * 对入参的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;
    }
}

我们看到最重要的代码其实是ObjectMapper 里面的 JsonHtmlXssDeserializer,这个解析器是解析json字符串时调用的,我们在里面对解析出来的参数进行转义就可以了。

方法三(不行):

网上还有一个方法是替换默认的ObjectMapper的,从第二种方法我们可以看出来,其实最终是为了替换默认的ObjectMapper,于是乎网上有了这种写法

 /**
 * 过滤json类型的
 * @param builder
 * @return
 */
 @Bean
 @Primary
 public ObjectMapper xssObjectMapper(Jackson2ObjectMapperBuilder builder) {
 //解析器
 ObjectMapper objectMapper = builder.createXmlMapper(false).build();
 //注册xss解析器
 SimpleModule xssModule = new SimpleModule("XssStringJsonSerializer");
 xssModule.addSerializer(new XssStringJsonSerializer());
 objectMapper.registerModule(xssModule);
 //返回
 return objectMapper;
 }

也是想用 @bean 注解来替换默认的ObjectMapper,这样真的可以吗,这样其实跟用@Bean注解替换 MappingJackson2XmlHttpMessageConverter 是一样的,我们看下源码

默认是用Jackson2ObjectMapperBuilder来构造ObjectMapper的,我们进去build方法看一下,可以看到也是new出来的,并没有被spring容器管理,所以这种方法不可以

四、总结

还是那句话,网上很多的文章代码估计不知道测过没有,拿来用很多都不适用,我们可以拿来参考,找到其中的思路,再自己分析原理,理解透了这样子才能真正解决自己的问题。最后欢迎大家到我的个人网站看看,点这里




  • 微信
  • 赶快加我聊天吧
  • QQ
  • 相逢即是有缘
  • weinxin
三尾鱼

发表评论 取消回复

    • avatar 理想三巡

      真的是,看了好多就这个能用