Springboot 防止XSS攻击的一部分解决方案

1. XSS与处理思路

XSS攻击全称跨站脚本攻击,是一种在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中。解决方案可以分为将用户输入的数据无害化处理或者将用户输入数据中有害的部分去除两种思路。

无害化可以使用类库 owasp-java-html-sanitizer 来处理

<dependency>
    <groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
    <artifactId>owasp-java-html-sanitizer</artifactId>
    <version>20211018.2</version>
</dependency>

去除有害部分可以使用 jsoup 来处理

<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.16.1</version>
</dependency>

这里由于需要使用富文本框输入文本,并在展示时保留样式,我使用的是 jsoup 来过滤用户输入时的有害部分的做法。

 

2. 处理方法

2.1 创建XSS过滤工具类

复制代码
import org.jsoup.Jsoup;
import org.jsoup.safety.Whitelist;

/**
 * XSS过滤工具类
 */
public class XssUtils extends Whitelist {

    /**
     * XSS过滤
     */
    public static String filter(String html){
        return Jsoup.clean(html, xssWhitelist());
    }

    /**
     * XSS过滤白名单
     */
    private static Whitelist xssWhitelist(){
        return new Whitelist()
            //支持的标签
            .addTags("a", "b", "blockquote", "br", "caption", "cite", "code", "col", "colgroup", "dd", "div", "dl",
                    "dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", "i", "img", "li", "ol", "p", "pre", "q", "small",
                    "strike", "strong","sub", "sup", "table", "tbody", "td","tfoot", "th", "thead", "tr", "u","ul",
                    "embed","object","param","span")

            //支持的标签属性
            .addAttributes("a", "href", "class", "style", "target", "rel", "nofollow")
            .addAttributes("blockquote", "cite")
            .addAttributes("code", "class", "style")
            .addAttributes("col", "span", "width")
            .addAttributes("colgroup", "span", "width")
            .addAttributes("img", "align", "alt", "height", "src", "title", "width", "class", "style")
            .addAttributes("ol", "start", "type")
            .addAttributes("q", "cite")
            .addAttributes("table", "summary", "width", "class", "style")
            .addAttributes("tr", "abbr", "axis", "colspan", "rowspan", "width", "style")
            .addAttributes("td", "abbr", "axis", "colspan", "rowspan", "width", "style")
            .addAttributes("th", "abbr", "axis", "colspan", "rowspan", "scope","width", "style")
            .addAttributes("ul", "type", "style")
            .addAttributes("pre", "class", "style")
            .addAttributes("div", "class", "id", "style")
            .addAttributes("embed", "src", "wmode", "flashvars", "pluginspage", "allowFullScreen", "allowfullscreen",
                "quality", "width", "height", "align", "allowScriptAccess", "allowscriptaccess", "allownetworking", "type")
            .addAttributes("object", "type", "id", "name", "data", "width", "height", "style", "classid", "codebase")
            .addAttributes("param", "name", "value")
            .addAttributes("span", "class", "style")

            //标签属性对应的协议
            .addProtocols("a", "href", "ftp", "http", "https", "mailto")
            .addProtocols("img", "src", "http", "https")
            .addProtocols("blockquote", "cite", "http", "https")
            .addProtocols("cite", "cite", "http", "https")
            .addProtocols("q", "cite", "http", "https")
            .addProtocols("embed", "src", "http", "https");
    }
}
复制代码

 

2.2 表单、URL、header传参方式提交数据的参数处理

创建XssFilter

复制代码
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * XSS过滤
 */
public class XssFilter implements Filter {

    @Override
    public void init(FilterConfig config) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper(
                (HttpServletRequest) request);
        chain.doFilter(xssRequest, response);
    }

    @Override
    public void destroy() {
    }
}
复制代码

 

创建XssHttpServletRequestWrapper,重写请求参数处理函数,这是实现XSS过滤的关键,在其内重写了getParameter,getParameterValues,getHeader等方法,对http请求内的参数进行了过滤。

复制代码
import org.apache.commons.lang3.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.LinkedHashMap;
import java.util.Map;


/**
 * XSS过滤处理
 */
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
    HttpServletRequest orgRequest;

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

    @Override
    public String getParameter(String name) {
        String value = super.getParameter(xssEncode(name));
        if (StringUtils.isNotBlank(value)) {
            value = xssEncode(value);
        }
        return value;
    }

    @Override
    public String[] getParameterValues(String name) {
        String[] parameters = super.getParameterValues(name);
        if (parameters == null || parameters.length == 0) {
            return null;
        }

        for (int i = 0; i < parameters.length; i++) {
            parameters[i] = xssEncode(parameters[i]);
        }
        return parameters;
    }

    @Override
    public Map<String,String[]> getParameterMap() {
        Map<String,String[]> map = new LinkedHashMap<>();
        Map<String,String[]> parameters = super.getParameterMap();
        for (String key : parameters.keySet()) {
            String[] values = parameters.get(key);
            for (int i = 0; i < values.length; i++) {
                values[i] = xssEncode(values[i]);
            }
            map.put(key, values);
        }
        return map;
    }

    @Override
    public String getHeader(String name) {
        String value = super.getHeader(xssEncode(name));
        if (StringUtils.isNotBlank(value)) {
            value = xssEncode(value);
        }
        return value;
    }

    private String xssEncode(String input) {
        return XssUtils.filter(input);
    }

    /**
     * 获取最原始的request
     */
    public HttpServletRequest getOrgRequest() {
        return orgRequest;
    }

    /**
     * 获取最原始的request
     */
    public static HttpServletRequest getOrgRequest(HttpServletRequest request) {
        if (request instanceof XssHttpServletRequestWrapper) {
            return ((XssHttpServletRequestWrapper) request).getOrgRequest();
        }

        return request;
    }
}
复制代码

 

添加Filter配置

复制代码
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;

import javax.servlet.DispatcherType;

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean xssFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setDispatcherTypes(DispatcherType.REQUEST);
        registration.setOrder(Integer.MAX_VALUE);
        registration.setFilter(new XssFilter());
        registration.addUrlPatterns("/*");
        registration.setName("xssFilter");
        return registration;
    }
}
复制代码

 

2.3 JSON方式提交数据的参数处理

Spring处理JSON是通过MappingJackson2HttpMessageConverter实现的,所以可以利用JSON反序列化时对JSON内容进行无害化处理。

注意:这种处理方式对  @RequestBody String params 的参数无效,如下:

@RequestMapping("test")
public String test(@RequestBody String data) {
}

接下来是实现方式:

首先创建 XssStringDeserializer 

复制代码
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 java.io.IOException;

/**
 * JSON反序列化String类型时防XSS的处理器
 */
public class XssStringDeserializer extends JsonDeserializer<String> {
    public static final XssStringDeserializer instance = new XssStringDeserializer();

    @Override
    public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        String source = jsonParser.getText().trim();
        return XssUtils.filter(source);
    }
}
复制代码

 

将XssStringDeserializer注册到MappingJackson2HttpMessageConverter中

复制代码
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * Web配置
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Bean
    public MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        ObjectMapper mapper = new ObjectMapper();

        //String类型防XSS
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addDeserializer(String.class, XssStringDeserializer.instance);
        mapper.registerModule(simpleModule);

        converter.setObjectMapper(mapper);
        return converter;
    }
}
复制代码

 

posted @   安培昌浩  阅读(1675)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示