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容器管理,所以這種方法不可以

四、總結

還是那句話,網上很多的文章代碼估計不知道測過沒有,拿來用很多都不適用,我們可以拿來參考,找到其中的思路,再自己分析原理,理解透了這樣子才能真正解決自己的問題。最後歡迎大家到我的個人網站看看,點這裏

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章