您现在的位置是:首页 > 技术人生 > 后端技术后端技术

SpringBoot添加XSS过滤器

高晓波2021-09-14【后端技术】人已围观

简介XSS攻击大家应该不太陌生,主要是通过向后台提交恶意代码,实现自己不可告人的秘密。
那么怎么实现XSS过滤呢?网上看了一圈,很多文章说通过Filter + 重写JsonDeserializer实现对请求参数的拦截、过滤。该方法本身没毛病,可惜我做得这个项目有富文本编辑器,重写JsonDeserializer的方式相当于对json参数一刀切,而对于富文本编辑器传过来的参数我是需要放行的。

XSS攻击大家应该不太陌生,主要是通过向后台提交恶意代码,实现自己不可告人的秘密。

那么怎么实现XSS过滤呢?网上看了一圈,很多文章说通过Filter + 重写JsonDeserializer实现对请求参数的拦截、过滤。该方法本身没毛病,可惜我做得这个项目有富文本编辑器,重写JsonDeserializer的方式相当于对json参数一刀切,而对于富文本编辑器传过来的参数我是需要放行的。

所以本篇讲一下通过纯Filter的方式,实现一个XSS过滤器,以达到对部分url进行过滤,对部分url放行。放行的url毕竟是少数,再在其方法中进行精准控制,如:标题、作者等字段手工过滤,正文字段不过滤。

 


1、XSS过滤工具类

import java.util.Objects;

/**
 * @author gaoxiaobo
 * @description
 * @date 2021/9/13 1:30 下午
 */
public class XssUtil {
    public static String cleanXSS(String value) {
        if (Objects.isNull(value)) {
            return value;
        }
        //在这里自定义需要过滤的字符
        value = value.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
        value = value.replaceAll("\\(", "&#40;").replaceAll("\\)", "&#41;");
        value = value.replaceAll("'", "&#39;");
        value = value.replaceAll("eval((.*))", "");
        value = value.replaceAll("[\"'][s]*javascript:(.*)[\"']", "\"\"");
        value = value.replaceAll("<script>", "");
        return value;
    }
}


2、XSS请求处理 XssHttpServletRequestWrapper


import com.gaoxiaobo.tms.utils.XssUtil;
import com.google.common.net.HttpHeaders;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.HandlerMapping;

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.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * @author gaoxiaobo
 * @description
 * @date 2021/9/13 1:29 下午
 */
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {

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

    /**
     * 对header处理
     * @param name
     * @return
     */
    @Override
    public String getHeader(String name) {
        String value = super.getHeader(name);
        return XssUtil.cleanXSS(value);
    }

    /**
     * 对参数处理
     * @param name
     * @return
     */
    @Override
    public String getParameter(String name) {
        String value = super.getParameter(name);
        return XssUtil.cleanXSS(value);
    }

    /**
     * 对数值进行处理
     * @param name
     * @return
     */
    @Override
    public String[] getParameterValues(String name) {
        String[] values = super.getParameterValues(name);
        if (values != null) {
            int length = values.length;
            String[] escapseValues = new String[length];
            for (int i = 0; i < length; i++) {
                escapseValues[i] = XssUtil.cleanXSS(values[i]);
            }
            return escapseValues;
        }
        return super.getParameterValues(name);
    }

    /**
     * 主要是针对HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE 获取pathvalue的时候把原来的pathvalue经过xss过滤掉
     */
    @Override
    public Object getAttribute(String name) {
        // 获取pathvalue的值
        if (HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE.equals(name)) {
            Map uriTemplateVars = (Map) super.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
            if (Objects.isNull(uriTemplateVars)) {
                return uriTemplateVars;
            }
            Map newMap = new LinkedHashMap<>();
            uriTemplateVars.forEach((key, value) -> {
                if (value instanceof String) {
                    newMap.put(key, XssUtil.cleanXSS((String) value));
                } else {
                    newMap.put(key, value);

                }
            });
            return newMap;
        } else {
            return super.getAttribute(name);
        }
    }

    /**
     * 对json处理
     * @return
     * @throws IOException
     */
    @Override
    public ServletInputStream getInputStream() throws IOException {
        // 非json类型,直接返回
        if (!isJsonRequest()) {
            return super.getInputStream();
        }
        
        List<String> lines = IOUtils.readLines(super.getInputStream(), UTF_8);
        String json = String.join("\n", lines);

        // 为空,直接返回
        if (StringUtils.isBlank(json)) {
            return super.getInputStream();
        }

        // xss过滤
        json = XssUtil.cleanXSS(json);
        final ByteArrayInputStream bis = new ByteArrayInputStream(json.getBytes(UTF_8));
        return new ServletInputStream() {
            @Override
            public boolean isFinished(){
                return true;
            }

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

            @Override
            public void setReadListener(ReadListener readListener) {
            }

            @Override
            public int read(){
                return bis.read();
            }
        };
    }

    /**
     * 是否是Json请求
     */
    private boolean isJsonRequest() {
        String header = super.getHeader(HttpHeaders.CONTENT_TYPE);
        return MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(header) || MediaType.APPLICATION_JSON_UTF8_VALUE.equalsIgnoreCase(header);
    }
}


3、XSS过滤器XSSFilter

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

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

/**
 * @author gaoxiaobo
 * @description
 * @date 2021/9/13 1:34 下午
 */
@Component
public class XSSFilter implements Filter {
    /**
     * 排除链接
     */
    @Value("#{'${xss.excludes}'.split(',')}")
    private List<String> excludes;

    @Value("${xss.enabled}")
    private Boolean enabled;


    @Override
    public void doFilter(ServletRequest req, ServletResponse res,
                         FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request;
        /**
         * 判断是否进行xss过滤
         */
        if (!handleExcludeUrl(req)){
            request = new XssHttpServletRequestWrapper((HttpServletRequest) req);
        }else {
            request = (HttpServletRequest) req;
        }
      
        chain.doFilter(request, response);
    }

    public void init(FilterConfig filterConfig) {
    }

    public void destroy() {
    }

    /**
     * 判断当前路径是否需要过滤
     */
    private boolean handleExcludeUrl(ServletRequest request) {
        if (!enabled) {
            return true;
        }
        if (excludes == null || excludes.isEmpty()) {
            return false;
        }
        HttpServletRequest req = (HttpServletRequest)request;
        String url = req.getServletPath();
        for (String pattern : excludes) {
            Pattern p = Pattern.compile("^" + pattern);
            Matcher m = p.matcher(url);
            if (m.find()) {
                return true;
            }
        }
        return false;
    }
}


4、在application.yml添加xss配置

xss:
  enabled: true
  excludes: /cms/article/create-save,/cms/article/create-publish,/cms/article/edit-save,/cms/article/edit-publish

至此,一个粒度为URL级别的XSS过滤器就搞定了。对于上述忽略的url如果并非所有请求参数都需要忽略,只需要在方法中手工过滤即可。
 

Tags:java安全防护

很赞哦! ()

文章评论