欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-06-21 15:41:13
目录springboot使用过滤器,jsoup过滤xss脚本1.把可能包含脚本的参数位置分析一下2.分析实现过程3.代码实现过程使用jsoup防止xss攻击springboot使用过滤器,jsoup过...

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在这期间,

  • 需要准备好jsoup过滤脚本的工具类;
  • 需要自定义一个过滤器,并且在过滤器中添加匹配条件,如:那些url不需要过滤,那些请求方式必须进行过滤;
  • 对过滤器进行配置,是否开启,设置在整个过滤器链中的位置,设置过滤的白名单或者黑名单
  • 所以就很清晰了我们过滤需要哪些类,哪些配置了

一个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;
    }
}

调试截图:

请求:

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

程序截图:

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

运行结果:

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

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

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

感谢luckpet大佬的提示

1 bufferedreader 使用完需要关闭 ;

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

使用jsoup防止xss攻击

前阵子项目国测后,打开一个项目页面,莫名其妙弹出xss,搜了全局也没找到alert("xss"),问了一下项目经理,原来是国测做防注入的时候,在添加数据的时候做的,一脸懵逼。

查了一下资料,以前做项目的时候都没想到这个问题,如果保存一段script脚本,查数据的时候,这段脚本就会被执行,这东西后果挺严重啊,如果是在桌面外弹框,执行个挖矿脚本,这玩意不得了啊,厉害,长知识了。。。

<dependency>
 <groupid>org.jsoup</groupid>
 <artifactid>jsoup</artifactid>
 <version>1.11.3</version>
</dependency>

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。