springboot中使用过滤器,jsoup过滤XSS脚本

背景:略

目标:完成request请求中的脚本过滤

技术:filter,jsoup,requestWapper

 

1.把可能包含脚本的参数位置分析一下---------------------------------

post/put/delete: 请求的参数中,有可能是表单提交、也有可能是使用了@requestBody注解,那么参数就是json格式,位于request的流中。

get/options等:可能存在于url参数中,也有可能是表单提交的预请求中,所以一般在能想到的位置都有可能存在,包括header中。

 

2.分析实现过程:

2.1首先要从request请求中将各个需要过滤位置的参数取出来

2.2然后将参数取出来进行过滤

2.3将过滤后的参数重新包装成request传递下去

2.4在这期间,

1.需要准备好jsoup过滤脚本的工具类;

2.需要自定义一个过滤器,并且在过滤器中添加匹配条件,如:那些url不需要过滤,那些请求方式必须进行过滤;

3.对过滤器进行配置,是否开启,设置在整个过滤器链中的位置,设置过滤的白名单或者黑名单

 

所以就很清晰了我们过滤需要哪些类,哪些配置了

(1.一个filter,一个requestWapper,一个jsoup工具类,一个filter的配置类)

2.5进行数据测试

 

3.代码实现过程:

3.1.jsoup依赖:

<!--screen xss -->

<dependency>

<groupId>org.jsoup</groupId>

<artifactId>jsoup</artifactId>

<version>1.9.2</version>

</dependency>

3.2jsoup工具类:JsoupUtil

import org.jsoup.Jsoup;

import org.jsoup.nodes.Document;

import org.jsoup.safety.Whitelist;



import java.io.FileNotFoundException;

import java.io.IOException;



/**

* @Auther: qianshanmuxue

* @Date: 2019/2/27 19:32

* @Description: xss Illegal label filtering

*/

public class JsoupUtil {



private static final Whitelist whitelist = Whitelist.simpleText();//jsoup白名单种类,有四种,每一种针对的标签类型不一样,具体的可以ctrl+左键点击simpleText,在jsoup源码中有响应的注释和标签名单

//add myself white list label

private static final Document.OutputSettings outputSettings = new Document.OutputSettings().prettyPrint(false);

static {

whitelist.addAttributes(":all", "style").addTags("p").addTags("strong");//将自定义标签添加进白名单,除开白名单之外的标签都会被过滤

whitelist.preserveRelativeLinks(true);//这个配置的意思的过滤如果找不到成对的标签,就只过滤单个标签,而不用把后面所有的文本都进行过滤。

//(之前在这个问题上折腾了很久,当<script>标签只有一个时,会<script>标签后面的数据全部过滤)

}



public static String clean(String content) { //过滤方法

return Jsoup.clean(content, "", whitelist, outputSettings);

}



//test main

public static void main(String[] args) throws FileNotFoundException, IOException {

String text = "<a href=\"http://www.baidu.com/a\" onclick=\"alert(1);\"><strong><p>sss</p></strong></a><script>alert(0);</script>sss";

System.out.println(clean(text));

}



}

3.3request包装类XssHttpServletRequestWrapper

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

import com.xxx.utils.JsoupUtil;
import org.jsoup.nodes.Document;
import org.springframework.util.StringUtils;


/**
 * @Auther: qianshanmuxue
 * @Date: 2019/2/27 16:24
 * @Description:request wapper use to get request parameter and request bdoy data and wapper another request 
 */
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { //因为我们需要获取request中的数据,所以需要继承java底层中HttpServletRequestWrapper这个类,重写父类中的某些方法,获取相应位置的参数
    private HttpServletRequest orgRequest = null;

    private static final Document.OutputSettings outputSettings = new Document.OutputSettings().prettyPrint(false);

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

    }

    @Override 
    public ServletInputStream getInputStream() throws IOException {//get 

        BufferedReader br = new BufferedReader(new InputStreamReader(orgRequest.getInputStream()));
        String line = br.readLine();
        String result = "";
        if (line != null) {
            result += clean(line);
        }

        return new WrappedServletInputStream(new ByteArrayInputStream(result.getBytes()));
    }
    @Override
    public String getParameter(String name) {
        if (("content".equals(name) || name.endsWith("WithHtml"))) {
            return super.getParameter(name);
        }
        name = clean(name);
        String value = super.getParameter(name);
        if (!StringUtils.isEmpty(value)) {
            value = clean(value);
        }
        return value;
    }

    @Override
    public Map getParameterMap() {
        Map map = super.getParameterMap();
        // 返回值Map
        Map<String, String> returnMap = new HashMap<String, String>();
        Iterator entries = map.entrySet().iterator();
        Map.Entry entry;
        String name = "";
        String value = "";
        while (entries.hasNext()) {
            entry = (Map.Entry) entries.next();
            name = (String) entry.getKey();
            Object valueObj = entry.getValue();
            if (null == valueObj) {
                value = "";
            } else if (valueObj instanceof String[]) {
                String[] values = (String[]) valueObj;
                for (int i = 0; i < values.length; i++) {
                    value = values[i] + ",";
                }
                value = value.substring(0, value.length() - 1);
            } else {
                value = valueObj.toString();
            }
            returnMap.put(name, clean(value).trim());
        }
        return returnMap;
    }

    @Override
    public String[] getParameterValues(String name) {
        String[] arr = super.getParameterValues(name);
        if (arr != null) {
            for (int i = 0; i < arr.length; i++) {
                arr[i] = clean(arr[i]);
            }
        }
        return arr;
    }

    /**
     * get org request
     *
     * @return
     */
    public HttpServletRequest getOrgRequest() {
        return orgRequest;
    }

    /**
     * wapper request
     */
    public static HttpServletRequest getOrgRequest(HttpServletRequest req) {
        if (req instanceof XssHttpServletRequestWrapper) {
            return ((XssHttpServletRequestWrapper) req).getOrgRequest();
        }
        return req;
    }

    public String clean(String content) {
        String result = JsoupUtil.clean(content);
        return result;
    }

    private class WrappedServletInputStream extends ServletInputStream {
        public void setStream(InputStream stream) {
            this.stream = stream;
        }

        private InputStream stream;

        public WrappedServletInputStream(InputStream stream) {
            this.stream = stream;
        }

        @Override
        public int read() throws IOException {
            return stream.read();
        }

        @Override
        public boolean isFinished() {
            return true;
        }

        @Override
        public boolean isReady() {
            return true;
        }

        @Override
        public void setReadListener(ReadListener readListener) {

        }
    }
}

3.4filter-XssFilter

import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils; 
import java.io.IOException; 
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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;
import javax.servlet.http.HttpServletResponse;

/**
 * @Auther: qianshanmuxue
 * @Date: 2019/2/27 16:25
 * @Description:
 */
//@WebFilter
//@Component   在这里可以不用这个注解,以为后面我们会在config中去配置这个filter,在这里只需要实现 Filter  接口实现相应的方法就ok
public class XssFilter implements Filter {
    private static boolean IS_INCLUDE_RICH_TEXT = false;//用于接收配置中的参数,决定这个过滤器是否开启
    public List<String> excludes = new ArrayList<String>();//用于接收配置中的参数,决定哪些是不需要过滤的url(在这里,也可以修改handleExcludeURL()方法中相应的代码,使其变更为只需要过滤的url)
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        if (handleExcludeURL(req, resp)) {
            chain.doFilter(request, response);
            return;
        }
        XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
        chain.doFilter(xssRequest, response);
    }
/**
*此方法是决定对当前url是否执行过滤,
*在这里没有使用请求方法(post/put)来匹配,因为在本项目中使用url匹配更适合(因为get和其他请求方式也需要进行过滤),如果你有兴趣可以把这个方法更改为匹配请求方法进行过滤
**/
    private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) {
        if ((excludes == null || excludes.isEmpty())&&IS_INCLUDE_RICH_TEXT) {
            return false;
        }
        String url = request.getServletPath();
        for (String pattern : excludes) {
            Pattern p = Pattern.compile("^" + pattern);
            Matcher m = p.matcher(url);
            if (m.find()) {
                return true;
            }
        }
        return false;
    }
/**
 *过滤器初始化,从配置类中获取参数,用于初始化两个参数(是否开启,排除指定的url list)
 *
 */
    @Override
    public void init(FilterConfig arg0) throws ServletException {
        String isIncludeRichText = arg0.getInitParameter("isIncludeRichText");
        if (StringUtils.isNotBlank(isIncludeRichText)) {
            IS_INCLUDE_RICH_TEXT = BooleanUtils.toBoolean(isIncludeRichText);
        }

        String temp = arg0.getInitParameter("excludes");
        if (temp != null) {
            String[] url = temp.split(",");
            for (int i = 0; url != null && i < url.length; i++) {
                excludes.add(url[i]);
            }
        }
    }

    @Override
    public void destroy() {
    }
}

3.5filter的配置类:XssConfig

import com.xxx.filter.XssFilter;
import com.google.common.collect.Maps;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

/**
 * @Auther: qianshanmuxue
 * @Date: 2019/2/27 16:49
 * @Description: xss filter config
 */
@Configuration
public class XssConfig {
    @Bean
    public FilterRegistrationBean xssFilterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new XssFilter());
        filterRegistrationBean.setOrder(1);//filter order ,set it first
        filterRegistrationBean.setEnabled(true);
        filterRegistrationBean.addUrlPatterns("/*"); //set filter all url mapping
        Map<String, String> initParameters = Maps.newHashMap();
        initParameters.put("excludes", "/oauth/token");///white list url
        initParameters.put("isIncludeRichText", "true");//enable or disable
        filterRegistrationBean.setInitParameters(initParameters);
        return filterRegistrationBean;
    }
}

调试截图:

请求:

 

 

程序截图:

运行结果:

可以看到body中 的脚本已经被过滤了,

然后其他的截图我就不发了,还有一种思路就是在过滤器中把字符转义。

感谢luckpet大佬的提示

1 BufferedReader 使用完需要关闭 ;

2 对于一些拿postman等工具的朋友,拼接json的话会有换行 这里result += clean(line); 需要改成: while((line = br.readLine()) != null){ if (line != null) { result += line; } }

 

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值