前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Springboot 防止XSS攻击,包含解决RequestBody 的Json 格式参数

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

作者头像
全栈程序员站长
发布2022-07-05 09:46:09
1.4K0
发布2022-07-05 09:46:09
举报

一、前言

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

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

XssHttpServletRequestWrapper、XssFilter

代码语言:javascript
复制
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;
    }

}
代码语言:javascript
复制
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转义,理所当然的想到如下办法

代码语言:javascript
复制
@Bean
HttpMessageConverter<?> MappingJackson2HttpMessageConverter() {

}

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

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

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

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

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

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

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

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

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

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

方法一:

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

方法二:

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

代码语言:javascript
复制
@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,于是乎网上有了这种写法

代码语言:javascript
复制
 /**
 * 过滤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 是一样的,我们看下源码

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

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

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

四、总结

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

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/111314.html原文链接:https://javaforall.cn

本文参与?腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021年8月3,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体同步曝光计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、前言
  • 二、网上大多数的解决方案
  • 三、RequestBody注解接收json格式参数解决方法
    • 方法一:
      • 方法二:
        • 方法三(不行):
        • 四、总结
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
        http://www.vxiaotou.com