SpringBoot实现的XSS拦截,一种简单的方式

这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

目标

使用 Spring Boot 的 Filter 对参数拦截,使用 Jsoup 对 参数中的 XSS进行过滤

工具

  • Spring Boot 2.0
  • Jsoup (可选)

实现原理

Spring Boot 的 Filter 拦截到前端的参数后进行过滤(看着是不是很简单??)。

说白了就是两个功能:参数拦截、脚本过滤。

参数拦截

想要过滤XSS首先要能拦截到前端的参数。

先写个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.http.HttpServletRequest;
  
public class XSSEscapeFilter implements Filter {  
   
  
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
       
    }  
  
    @Override
    public void destroy() {
       
    }  
  
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    	//后面会有 XssHttpServletRequestWrapper 的代码。这个类是自己定义的
        chain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) request), response);
    }
}

复制代码

这个Filter 是可以拦截到请求的,但是呢,如果想要对参数进行修改就需要重新定义 HttpServletRequestWrapper,只有用自定义的HttpServletRequestWrapper 才能对参数进行修改。

下面定义 XssHttpServletRequestWrapper:


import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.safety.Whitelist;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * 实现XSS过滤
 * Create by zdRan on 2018/5/8
 *
 * @author cm.zdran@gmail.com
 */
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
    private HttpServletRequest orgRequest = null;

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

    }

    @Override
    public String getParameter(String name) {
       	// 对参数进行修改
        return name;
    }

    @Override
    public Map getParameterMap() {
       	// 对参数进行修改
        return super.getParameterMap();;
    }

    @Override
    public String[] getParameterValues(String name) {
        String[] arr = super.getParameterValues(name);
        // 对参数进行修改
        return arr;
    }

    @Override
    public String getHeader(String name) {
    	//对参数进行修改
        return super.getHeader(name);;
    }

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

    /**
     * 获取最原始的request的静态方法
     *
     * @return
     */
    public static HttpServletRequest getOrgRequest(HttpServletRequest req) {
        if (req instanceof XssHttpServletRequestWrapper) {
            return ((XssHttpServletRequestWrapper) req).getOrgRequest();
        }

        return req;
    }
复制代码

这样就能对参数进行修改了,但是,目前的情况还不能处理POST请求,或者 RequestBody 注解。

当使用 RequestBody 注解时,你会发现,重写的这几个方法都没有走,说明我们没有重写全方法。

找了一些资料发现:RequestBody注解读取参数的方法是getInputStream()

我们重写一下这个方法:

 @Override
    public ServletInputStream getInputStream() throws IOException {

        BufferedReader br = new BufferedReader(new InputStreamReader(orgRequest.getInputStream()));
        String line = br.readLine();
        String result = "";
        if (line != null) {
            //对参数进行处理
        }

        return new WrappedServletInputStream(new ByteArrayInputStream(result.getBytes()));
    }
复制代码

然后启动这个 Filter


import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.DispatcherType;

/**
 * Create by zdRan on 2018/5/8
 *
 * @author cm.zdran@gmail.com
 */
@Configuration
public class XssFilterConfiguration {
    /**
     * xss过滤拦截器
     */
    @Bean
    public FilterRegistrationBean xssFilterRegistrationBean() {
        FilterRegistrationBean initXssFilterBean = new FilterRegistrationBean();
        initXssFilterBean.setFilter(new XSSEscapeFilter());
        initXssFilterBean.setOrder(1);
        initXssFilterBean.setEnabled(true);
        initXssFilterBean.addUrlPatterns("/*");
        initXssFilterBean.setDispatcherTypes(DispatcherType.REQUEST);
        return initXssFilterBean;
    }
}

复制代码

到这里基本上就拦截到参数了,你可以自己定义对参数的修改规则。也可以使用jsoup对XSS进行过滤

脚本过滤

使用 jsoup 对参数中的 标签进行过滤
添加依赖

<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.11.3</version>
</dependency>
复制代码

好了。到这就算结束了,不过目前还有一个小问题。

使用 Jsoup 是可以过滤掉所有的html标签,但是也有个问题,比如

参数是: {"name":"<html","passwd":"12345"},过滤后的结果是:{"name":"

因为没有找到<html>标签的结束位置,所以就会过滤掉后面所有的参数。

这样就会导致 controller 获取参数的时候异常。

但是这种 html 标签即便是返回给前端,浏览器也无法解析,因为标签是错误的。如果你真的需要过滤这种参数。

可以尝试直接过滤特殊字符。