springboot 防止XSS攻擊,包括使用 @RequestBody 接收的 Json 數據處理

前言

最近項目需要做一些軟件測評、軟件安全測試等內容,然後就發現項目上一些漏洞,這次就針對 XSS 攻擊做一個詳細的處理。

一、什麼是 XSS ?

XSS攻擊通常指的是通過利用網頁開發時留下的漏洞,通過巧妙的方法注入惡意指令代碼到網頁,使用戶加載並執行攻擊者惡意製造的網頁程序。這些惡意網頁程序通常是JavaScript,但實際上也可以包括Java、 VBScript、ActiveX、 Flash 或者甚至是普通的HTML。攻擊成功後,攻擊者可能得到包括但不限於更高的權限(如執行一些操作)、私密網頁內容、會話和cookie等各種內容。— 來源於百度百科

二、深入理解 XSS

簡單來說就是這樣的情況,比如我現在有個保存用戶的表單,需要輸入用戶名和密碼,現在我用戶名,我輸入下面這串代碼

<script>alert('1');</script>

如果程序未進行處理,那麼很可能會出現圖片中的情況,彈出一個提示框
在這裏插入圖片描述
當然可能這只是一個小小的惡作劇,一個簡單的彈窗,要是換成cookie的發送呢?就可以用戶在不知情的情況下,獲取到用戶的cookie,會獲取用戶的一系列信息,從而進行其他用戶信息的盜取。

三、防止 XSS 攻擊方案

第一種方法,針對表單傳值的情況

網上看了很多資料,大部分都是這種處理方案

package com.wxw.springboot_xss.common;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
 
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringEscapeUtils;

/**
 * @author wuxiongwei
 * @date 2020/5/26 9:29
 * @Description
 */
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.wxw.springboot_xss.common;
 
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;
 

/**
 * @author wuxiongwei
 * @date 2020/5/26 9:29
 * @Description
 */
@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									傳參方式				接收方式
application/x-www-form-urlencoded			表單key-value	HttpServletRequest Parameters 獲取
multipart/form-data							表單key-value	HttpServletRequest Parameters 獲取

第二種方法,針對前後端分離使用 @RequestBody 接收 json 數據的情況,繼承 WebMvcConfigurationSupport 類

	Content-Type				傳參方式				接收方式
application/json				json格式文本		HttpServletRequest IO流獲取

繼承 WebMvcConfigurationSupport 類後,重寫extendMessageConverters,修改已經配置好的轉化器列表,遍歷轉化器列表,找到MappingJackson2HttpMessageConverter,可以根據類型來判斷哪個是 MappingJackson2HttpMessageConverter ,然後移除(注意遍歷移除一定要用迭代器),把我們自定義的轉化器添加進去。

package com.wxw.springboot_xss.config;

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.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

/**
 * @author wuxiongwei
 * @date 2020/5/26 9:29
 * @Description
 */
@Configuration
public class WebMvcConfig  extends WebMvcConfigurationSupport {


        @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 JsonHtmlXssDeserializer1(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 JsonHtmlXssDeserializer1 extends JsonDeserializer<String> {

        public JsonHtmlXssDeserializer1(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;
        }
    }

使用此方法之後,在控制層跳轉頁面會出現靜態資源無法訪問的情況(可以自己重寫addResourceHandlers()這個方法來訪問資源文件)。
因爲繼承WebMvcConfigurationSupport 會使原本的 springboot 的自動配置類失效,所以框架幫我們自動配置的就沒了

第三種方法,針對前後端分離使用 @RequestBody 接收 json 數據的情況,實現 WebMvcConfigurer、InitializingBean 類 (建議使用)

	Content-Type				傳參方式				接收方式
application/json				json格式文本		HttpServletRequest IO流獲取
package com.wxw.springboot_xss.config;

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;

/**
 * @author wuxiongwei
 * @date 2020/5/26 9:29
 * @Description
 */
@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;
            }
        }



四、測試防止 XSS 攻擊

測試實體類

package com.wxw.springboot_xss.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author wuxiongwei
 * @date 2020/5/26 11:51
 * @Description
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class User {
    private String name;
    private int age;
}

測試 controller

package com.wxw.springboot_xss.controller;

import com.wxw.springboot_xss.entity.User;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author wuxiongwei
 * @date 2020/5/26 11:18
 * @Description
 */
@RestController
public class TestController {

    @RequestMapping("/test")
    public String test(@RequestBody User user){
        System.out.println(user.getName());
        return  user.getName();
    }
}

如下圖
使用 postman 模擬 XSS 攻擊測試,請求之後,html 文件會轉換成對應的字符串

在這裏插入圖片描述

至此就大功告成了。

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