业务服务:xss攻击

时间:2024-03-29 11:57:44

文章目录

  • 前言
  • 一、使用注解预防
    • 1. 添加依赖
    • 2. 自定义注解
    • 3. 自定义校验逻辑
    • 4. 使用
  • 二、使用过滤器
    • 1. 添加配置
    • 2. 创建配置类
    • 3. 创建过滤器
    • 4. 创建过滤器类
    • 5. 使用


前言

xss攻击时安全领域中非常常见的一种方法,保证我们的系统安全是非常重要的

xss攻击简单来说就是在用户输入内容中添加脚本< script >…< script >
这里面可能包含获取cookie,


一、使用注解预防

1. 添加依赖

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

2. 自定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Constraint(validatedBy = {XssValidator.class})
public @interface Xss {

    String message() default "不允许任何脚本运行";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

3. 自定义校验逻辑

public class XssValidator implements ConstraintValidator<Xss, String> {

    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
    	// 这里用的hutool的工具类
        return !ReUtil.contains(HtmlUtil.RE_HTML_MARK, value);
    }

}

4. 使用

创建实体类

@Data
public class Book {

    private Long id;

    private String name;

    @Xss
    private String content;
}

创建book控制器

@Validated
@RestController
@RequestMapping("/book")
public class BookController {



    @PostMapping
    public void save(@Validated @RequestBody Book book){
        System.out.println(book);
    }
}

发送请求

在这里插入图片描述

可以看到系统抛出了异常,这样我们就成功了使用注解完成了脚本验证

在这里插入图片描述

二、使用过滤器

注解的方式需要一个一个的添加,这显然是不太方便的。我们可以通过过滤器的方式对前端传递过来的参数进行统一处理

1. 添加配置

# 防止XSS攻击
xss:
  # 过滤开关
  enabled: true
  # 排除链接(多个用逗号分隔)
  excludes: 
  # 匹配链接
  urlPatterns: /book/*

2. 创建配置类

@Data
@Component
@ConfigurationProperties(prefix = "xss")
public class XssProperties {

    /**
     * 过滤开关
     */
    private String enabled;

    /**
     * 排除链接(多个用逗号分隔)
     */
    private String excludes;

    /**
     * 匹配链接
     */
    private String urlPatterns;

}

3. 创建过滤器

@Configuration
public class FilterConfig {

    @Autowired
    private XssProperties xssProperties;

	// 关闭校验注解
    @SuppressWarnings({"rawtypes", "unchecked"})
    @Bean
    // xss.enabled==true时,注入bean
    @ConditionalOnProperty(value = "xss.enabled", havingValue = "true") 
    public FilterRegistrationBean xssFilterRegistration() {
    	// 创建过滤器注册器
        FilterRegistrationBean registration = new FilterRegistrationBean();
		
		// 设置运行类型
        registration.setDispatcherTypes(DispatcherType.REQUEST);
		
		// 设置过滤器
        registration.setFilter(new XssFilter());
	
		// 添加拦截路径
        registration.addUrlPatterns(StrUtil.split(xssProperties.getUrlPatterns(), StrUtil.C_COMMA).toArray(String[]::new));
        registration.setName("xssFilter");

		// 设置优先级为最高
        registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);

		// 添加自定义参数
        Map<String, String> initParameters = new HashMap<String, String>();
        initParameters.put("excludes", xssProperties.getExcludes());
        registration.setInitParameters(initParameters);

        return registration;
    }
}

4. 创建过滤器类

public class XssFilter implements Filter {
    /**
     * 排除链接
     */
    public List<String> excludes = new ArrayList<>();

    /**
     * 初始化的时候将排除连接根据,分割添加到excludes中
     * @param filterConfig
     * @throws ServletException
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String tempExcludes = filterConfig.getInitParameter("excludes");
        if (StrUtil.isNotBlank(tempExcludes)) {
            String[] url = tempExcludes.split(StrUtil.COMMA);
            excludes.addAll(Arrays.asList(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);
    }

    /**
     * 判断是否为排除过滤路径
     * @param request
     * @param response
     * @return
     */
    private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) {
        String url = request.getServletPath();
        String method = request.getMethod();
        // GET DELETE 不过滤
        if (method == null || HttpMethod.GET.matches(method) || HttpMethod.DELETE.matches(method)) {
            return true;
        }
        return matches(url, excludes);
    }
    public static boolean matches(String str, List<String> strs) {
        if (StrUtil.isBlank(str) || CollUtil.isEmpty(strs)) {
            return false;
        }
        for (String pattern : strs) {
            if (isMatch(pattern, str)) {
                return true;
            }
        }
        return false;
    }
    public static boolean isMatch(String pattern, String url) {
        AntPathMatcher matcher = new AntPathMatcher();
        return matcher.match(pattern, url);
    }

    @Override
    public void destroy() {

    }
}
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
    /**
     * @param request
     */
    public XssHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
    }

    /**
     * 将url拼接的参数进行脚本过滤
     * @param name
     * @return
     */
    @Override
    public String[] getParameterValues(String name) {
        String[] values = super.getParameterValues(name);
        if (values != null) {
            int length = values.length;
            String[] escapesValues = new String[length];
            for (int i = 0; i < length; i++) {
                // 防xss攻击和过滤前后空格
                escapesValues[i] = HtmlUtil.cleanHtmlTag(values[i]).trim();
            }
            return escapesValues;
        }
        return super.getParameterValues(name);
    }

    /**
     * 对body的脚本参数进行过滤
     * @return
     * @throws IOException
     */
    @Override
    public ServletInputStream getInputStream() throws IOException {
        // 非json类型,直接返回
        if (!isJsonRequest()) {
            return super.getInputStream();
        }

        // 为空,直接返回
        String json = StrUtil.str(IoUtil.readBytes(super.getInputStream(), false), StandardCharsets.UTF_8);
        if (StringUtils.isEmpty(json)) {
            return super.getInputStream();
        }

        // xss过滤
        json = HtmlUtil.cleanHtmlTag(json).trim();
        byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8);
        final ByteArrayInputStream bis = IoUtil.toStream(jsonBytes);
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return true;
            }

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

            @Override
            public int available() throws IOException {
                return jsonBytes.length;
            }

            @Override
            public void setReadListener(ReadListener readListener) {
            }

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

    /**
     * 是否是Json请求
     */
    public boolean isJsonRequest() {
        String header = super.getHeader(HttpHeaders.CONTENT_TYPE);
        return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE);
    }
}

5. 使用

发送请求

在这里插入图片描述

可以看到传递的脚本被成功过滤掉

在这里插入图片描述