spring boot xss拦截+OncePerRequestFilter+application/json

spring-boot-xss

版本

  • jdk1.8
  • spring boot 2.3.0

概念

  • XSS攻击全称跨站脚本攻击,是一种在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中。
  • 实例
将该字段存入数据库之后,当前端铺入div的时候,会出现弹窗

解决

  • 可以通过Jsoup把xss恶性攻击的过滤出去
  • 参考:springboot 框架防止XSS攻击功能

不同点

  • 项目如果使用的过滤器为OncePerRequestFilter
    OncePerRequestFilter的init(FilterConfig filterConfig) final修饰导致无法继承
  • 传参形式Content-Type:application/json springboot 框架防止XSS攻击功能 的XssHttpServletRequestWrapper无法实现多body参数的过滤

解决方法

  • OncePerRequestFilter
package com.lizy.xss.config;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.filter.OncePerRequestFilter;

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

/**
 * @author lizhongyuan
 */
public class MyFilter extends OncePerRequestFilter {

    /**
     * 控制是否过滤富文本内容
     */
    private static boolean IS_INCLUDE_RICH_TEXT = true;

    /**
     * 跳过的路径
     */
    public List excludes = new ArrayList<>();


    @Override
    protected void initFilterBean() {
        FilterConfig filterConfig = Objects.requireNonNull(getFilterConfig());
        String isIncludeRichText = filterConfig.getInitParameter("isIncludeRichText");
        if (StrUtil.isNotBlank(isIncludeRichText)) {
            IS_INCLUDE_RICH_TEXT = BooleanUtil.toBoolean(isIncludeRichText);
        }
        String temp = filterConfig.getInitParameter("excludes");
        if (temp != null) {
            String[] url = temp.split(",");
            excludes.addAll(Arrays.asList(url));
        }
    }

    @Override
    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        if (handleExcludeUrl(request, response)) {
            filterChain.doFilter(request, response);
        }else {
            XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper(request, IS_INCLUDE_RICH_TEXT);
            filterChain.doFilter(xssRequest, response);
        }
    }

    private boolean handleExcludeUrl(HttpServletRequest request, HttpServletResponse response) {
        if (excludes == null || excludes.isEmpty()) {
            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;
    }

    @Override
    public void destroy() {
    }
}
  • application/json
  @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }
    
        /**
     * 
     * servlet中inputStream只能一次读取,后续不能再次读取inputStream
     * xss过滤body后,重新把流放入ServletInputStream中
     * 
*/ 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 ){ } } @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 = IoUtil.read(in).toString(); IoUtil.close(in); //空串处理直接返回 if( StrUtil.isBlank(body)){ return super.getInputStream(); } JSONObject b = dealBody(JSON.parseObject(body)); return new RequestCachingInputStream(b.toJSONString().getBytes()); } /** * @param jsonObject jsonObject * @return JSONObject */ private JSONObject dealBody(JSONObject jsonObject){ Map objectMap = jsonObject.getInnerMap(); objectMap.forEach(((key, value) -> { if (value instanceof JSONObject){ objectMap.put(key, dealBody((JSONObject) value)); }else if (value instanceof JSONArray){ ((JSONArray) value).forEach(arrayObject -> dealBody((JSONObject) arrayObject)); }else if (value instanceof String) { objectMap.put(key, xssFilter((String) value)); } })); return jsonObject; }
  • 源码传送门

你可能感兴趣的:(spring boot xss拦截+OncePerRequestFilter+application/json)