Springboot防止XSS跨站脚本攻击

系统渗透测试时被监测存在可利用XSS漏洞,解决方法如下:

1、Maven引用

<dependency>
	<groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.13.1</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>

2、HtmlFilter过滤类
使用Jsoup对相应内容进行过滤。
jsoup是一个HTML解析器。Jsoup提供的Whitelist对文本内容进行过滤,过滤掉字符、属性,但是又保留必要的富文本格式。

package com.ieslab.interactivequery.xssconfig;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.safety.Whitelist;

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

/**
 * @description: 静态创建HtmlFilter方法
 * @author: yiwenli
 * @create: 2021-11-18 10:11
 **/
public class HtmlFilter {
    /**
     * 默认使用relaxed()
     * 允许的标签: a, b, blockquote, br, caption, cite, code, col, colgroup, dd, 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。结果不包含标签rel=nofollow ,如果需要可以手动添加。
     */
    private Whitelist whiteList;

    /**
     * 配置过滤化参数,不对代码进行格式化
     */
    private Document.OutputSettings outputSettings;

    private HtmlFilter() { }

    /**
     * 静态创建HtmlFilter方法
     * @param whiteList 白名单标签
     * @param pretty 是否格式化
     * @return HtmlFilter
     */
    public static HtmlFilter create(Whitelist whiteList, boolean pretty) {
        HtmlFilter filter = new HtmlFilter();
        if (whiteList == null) {
            filter.whiteList = Whitelist.relaxed();
        }
        filter.outputSettings = new Document.OutputSettings().prettyPrint(pretty);
        return filter;
    }

    /**
     * 静态创建HtmlFilter方法
     * @return HtmlFilter
     */
    public static HtmlFilter create() {
        return create(null, false);
    }

    /**
     * 静态创建HtmlFilter方法
     * @param whiteList 白名单标签
     * @return HtmlFilter
     */
    public static HtmlFilter create(Whitelist whiteList) {
        return create(whiteList, false);
    }

    /**
     * 静态创建HtmlFilter方法
     * @param excludeTags 例外的特定标签
     * @param includeTags 需要过滤的特定标签
     * @param pretty      是否格式化
     * @return HtmlFilter
     */
    public static HtmlFilter create(List<String> excludeTags, List<String> includeTags, boolean pretty) {
        HtmlFilter filter = create(null, pretty);
        //要过滤的标签
        if (includeTags != null && !includeTags.isEmpty()) {
            String[] tags = (String[]) includeTags.toArray(new String[0]);
            filter.whiteList.removeTags(tags);
        }
        //例外标签
        if (excludeTags != null && !excludeTags.isEmpty()) {
            String[] tags = (String[]) excludeTags.toArray(new String[0]);
            filter.whiteList.addTags(tags);
        }
        return filter;
    }

    /**
     * 静态创建HtmlFilter方法
     * @param excludeTags 例外的特定标签
     * @param includeTags 需要过滤的特定标签
     * @return HtmlFilter
     */
    public static HtmlFilter create(List<String> excludeTags, List<String> includeTags) {
        return create( includeTags, excludeTags, false );
    }

    /**
     * @param content 需要过滤内容
     * @return 过滤后的String
     */
    public String clean(String content) {
        return Jsoup.clean(content, "", this.whiteList, this.outputSettings);

    }

    public static void main(String[] args) throws IOException {
        String text = "<a href=\"http://www.baidu.com/a\" οnclick=\"alert(1);\"></a><script>alert(0);</script><b style=\"xxx\" οnclick=\"<script>alert(0);</script>\">abc</>";
        System.out.println(HtmlFilter.create().clean(text));
    }
}

3、XssFilter过滤器

package com.ieslab.interactivequery.xssconfig;

import org.apache.commons.lang3.StringUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @description: XssFilter
 * @author: yiwenli
 * @create: 2021-11-18 10:12
 **/
public class XssFilter implements Filter {

    /**
     * 例外urls
     */
    private List<String> excludeUrls = new ArrayList<>();

    /**
     * 例外标签
     */
    private List<String> excludeTags = new ArrayList<>();

    /**
     * 需要过滤标签
     */
    private List<String> includeTags = new ArrayList<>();

    /**
     * 开关
     */
    public boolean enabled = false;

    /**
     * 编码
     */
    private String encoding = "UTF-8";

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String enabledStr = filterConfig.getInitParameter("enabled");
        String excludeUrlStr = filterConfig.getInitParameter("urlPatterns");
        String excludeTagStr = filterConfig.getInitParameter("excludes");
        String includeTagStr = filterConfig.getInitParameter("includes");
        String encodingStr = filterConfig.getInitParameter("encoding");

        if (StringUtils.isNotEmpty(excludeUrlStr)) {
            String[] url = excludeUrlStr.split(",");
            Collections.addAll(this.excludeUrls, url);
        }

        if (StringUtils.isNotEmpty(excludeTagStr)) {
            String[] url = excludeTagStr.split(",");
            Collections.addAll(this.excludeTags, url);
        }

        if (StringUtils.isNotEmpty(includeTagStr)) {
            String[] url = includeTagStr.split(",");
            Collections.addAll(this.includeTags, url);
        }

        if (StringUtils.isNotEmpty(enabledStr)) {
            this.enabled = Boolean.parseBoolean(enabledStr);
        }

        if (StringUtils.isNotEmpty(encodingStr)) {
            this.encoding = encodingStr;
        }

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        if (handleExcludeUrls(req, resp)) {
            chain.doFilter(request, response);
            return;
        }

        XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request, encoding, excludeTags, includeTags );
        chain.doFilter(xssRequest, response);

    }

    private boolean handleExcludeUrls(HttpServletRequest request, HttpServletResponse response) {
        if (!enabled) {
            return true;
        }
        if (excludeUrls == null || excludeUrls.isEmpty()) {
            return false;
        }
        String url = request.getServletPath();
        for (String pattern : excludeUrls) {
            Pattern p = Pattern.compile("^" + pattern);
            Matcher m = p.matcher(url);
            if (m.find()) {
                return true;
            }
        }
        return false;
    }
}


4、XSS过滤处理

package com.ieslab.interactivequery.xssconfig;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * @description: XSS过滤处理
 * @author: yiwenli
 * @create: 2021-11-18 10:13
 **/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {

    HttpServletRequest orgRequest;

    String encoding;

    HtmlFilter htmlFilter;

    private final static String JSON_CONTENT_TYPE =  "application/json";

    private final static String CONTENT_TYPE = "Content-Type";

    /**
     * @param request  HttpServletRequest
     * @param encoding 编码
     * @param excludeTags 例外的特定标签
     * @param includeTags 需要过滤的标签
     */
    public XssHttpServletRequestWrapper( HttpServletRequest request, String encoding, List<String> excludeTags, List<String> includeTags ){
        super( request );
        orgRequest = request;
        this.encoding = encoding;
        this.htmlFilter = HtmlFilter.create( excludeTags, includeTags );
    }

    /**
     *
     * @param request HttpServletRequest
     * @param encoding 编码
     */
    public XssHttpServletRequestWrapper( HttpServletRequest request, String encoding ){
        this( request, encoding, null, null  );
    }

    private String xssFilter( String input ){
        return htmlFilter.clean( input );
    }

    @Override
    public ServletInputStream getInputStream() throws IOException{
        // 非json处理
        if( !JSON_CONTENT_TYPE.equalsIgnoreCase( super.getHeader( CONTENT_TYPE ) ) ){
            return super.getInputStream();
        }
        InputStream in = super.getInputStream();
        String body = IOUtils.toString( in, encoding );
        IOUtils.closeQuietly( in );

        //空串处理直接返回
        if( StringUtils.isBlank( body ) ){
            return super.getInputStream();
        }

        // xss过滤
        body = xssFilter( body );
        return new RequestCachingInputStream( body.getBytes( encoding ) );

    }

    @Override
    public String getParameter( String name ){
        String value = super.getParameter( xssFilter( name ) );
        if( StringUtils.isNotBlank( value ) ){
            value = xssFilter( 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] = xssFilter( 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] = xssFilter( values[i] );
            }
            map.put( key, values );
        }
        return map;
    }

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

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

    /**
     * <b>
     * #获取最原始的request
     * </b>
     * @param request HttpServletRequest
     */
    public static HttpServletRequest getOrgRequest( HttpServletRequest request ){
        if( request instanceof XssHttpServletRequestWrapper ){
            return ((XssHttpServletRequestWrapper) request).getOrgRequest();
        }
        return request;
    }

    /**
     * <pre>
     * servlet中inputStream只能一次读取,后续不能再次读取inputStream
     * xss过滤body后,重新把流放入ServletInputStream中
     * </pre>
     */
    private static class RequestCachingInputStream extends ServletInputStream {
        private final ByteArrayInputStream inputStream;
        public RequestCachingInputStream(byte[] bytes) {
            inputStream = new ByteArrayInputStream(bytes);
        }

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

        @Override
        public boolean isFinished() {
            return inputStream.available() == 0;
        }

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

        @Override
        public void setReadListener( ReadListener readListener ){
        }

    }

}

5、springboot2.2.4.RELEASE中注册Filter

package com.ieslab.interactivequery.xssconfig;

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

import javax.servlet.DispatcherType;
import java.util.HashMap;
import java.util.Map;

/**
 * @description: XssFilterConfig
 * @author: yiwenli
 * @create: 2021-11-18 10:14
 **/
@Configuration
public class XssFilterConfig {

    @Value("${xss.enabled:true}")
    private String enabled;

    @Value("${xss.excludes:}")
    private String excludes;

    @Value("${xss.includes:}")
    private String includes;

    @Value("${xss.urlPatterns:/*}")
    private String urlPatterns;

    @Bean
    public FilterRegistrationBean<XssFilter> xssFilterRegistrationBean() {
        FilterRegistrationBean<XssFilter> registration = new FilterRegistrationBean<>();
        registration.setDispatcherTypes(DispatcherType.REQUEST);
        registration.setFilter(new XssFilter());
        registration.addUrlPatterns(urlPatterns.split(","));
        registration.setName("XssFilter");
        registration.setOrder(Integer.MAX_VALUE);
        Map<String, String> initParameters = new HashMap<>();
        initParameters.put("excludes", excludes);
        initParameters.put("includes", includes);
        initParameters.put("enabled", enabled);
        registration.setInitParameters(initParameters);
        return registration;
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值