springboot项目如何防止XSS攻击

 更新时间:2021年06月23日 08:53:23   作者:细肉云吞  
XSS攻击全称跨站脚本攻击,是一种在web应用中的计算机安全漏洞,允许恶意web用户将代码植入到提供给其它用户使用的页面中。本文介绍防止XSS攻击的方法

1. 什么是XSS攻击?

    XSS攻击全称跨站脚本攻击,是一种在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中。也就是作恶的用户通过表单提交一些前端代码,如果不做处理的话,这些前端代码将会在展示的时候被浏览器执行。

2. 如何防范?

有两种方式,一种是一些特殊字符转义,另一种是去除一些危险html元素。本文通过JSOUP库实现第二种方式。

现在问题是,在什么时候对XSS攻击的字符串进行处理呢?这就涉及到spring mvc的Controller中的method params什么时候注入的问题。下面对方法参数注入的原理进行简单说明。

2.1 什么时候注入请求参数

常见的控制器方法有下面几种,分别是通过GET获取请求参数,POST获取json或者form表单的参数

        /**
         * 通过url的path,获取请求参数
         */
        @ResponseBody
        @GetMapping("/xssByGetUsingPath/{content}")
        public String testGetUsingPath(@PathVariable(name = "content") String content) {
            return content;
        }       


         /**
         * 通过url的query params获取请求数据. 方法使用简单类型进行接收
         */
        @ResponseBody
        @GetMapping("/xssByGetUsingSimple")
        public String testUsingSimple(@RequestParam(name = "content") String content) {
            return content;
        }

        /**
         * 通过url的query params获取请求数据. 方法使用model进行接收
         */
        @ResponseBody
        @GetMapping("/xssByGetUsingModel")
        public String testGetUsingModel(BaseTest.Paper paper) {
            return paper.getContent();
        }

        /**
         * 通过 form 表单的方式获取数据. 方法使用简单类型进行接收
         */
        @ResponseBody
        @PostMapping("/xssByFormPostUsingSimple")
        public String testFormPostUsingSimple(@RequestParam(name = "content") String content) {
            return content;
        }

        /**
         * 通过 form 表单的方式获取数据. 方法使用model进行参数接收
         */
        @ResponseBody
        @PostMapping("/xssByFormPostUsingModel")
        public String testFormPostUsingModel(BaseTest.Paper paper) {
            return paper.getContent();
        }

        /**
         * 通过 request body 发送 json数据
         */
        @ResponseBody
        @PostMapping("/xssByPostJsonBody")
        public String testPostJsonBody(@RequestBody BaseTest.Paper paper) {
            return paper.getContent();
        }

大家都知道,在spring mvc中处理请求的入口在 DispatcherServlet 类中,其中 doDispatch() 方法完成所有核心功能。在该主流程中,HandlerAdapter 将会对HTTP请求进行 Controller 的方法调用,以及对请求结果进行转换,并封装为DispatcherServlet 类需要的 ModelAndView 。在这里,由于使用注解的方式进行 Controller 定义,所以 HandlerAdapter 的实现类为 RequestMappingHandlerAdapter 。RequestMappingHandlerAdapter 类的 handle() 方法中,委托给 HandlerMethodArgumentResolver 对每个 Controller 的 方法的每个参数进行解析,反射调用 Controller 的 方法后,再 委托 HandlerMethodReturnValueHandler 对反射调用的返回值进行处理。

至此,本文的主角出现了,也就是 HandlerMethodArgumentResolver 。我们看下有关于本文的HandlerMethodArgumentResolver 类继承关系。

上面的常见controller定义方法的 参数解析(注意,这里是说方法那里的参数解析) 对应 HandlerMethodArgumentResolver  的关系如下图:

现在,我们已经知道了他们都由什么样 HandlerMethodArgumentResolver  解析方法参数,我们继续分析他们之前的共同点,并得到在哪里对http传过来的数据进行XSS处理。

RequestResponseBodyMethodProcessor 是一个对request body的JSON数据反序列化的处理器,在 resolveArgument() 方法中,将会获取合适的 org.springframework.http.converter.HttpMessageConverter  类对string数据进行反序列化处理。springboot 在初始化的时候,已经默认注册了 jackson 的 MappingJackson2HttpMessageConverter ,并使用它对 JSON 数据进行反序列化操作。在 jackson 里面,JsonDeserializer 类 用于json数据的property的反序列化,因此,我们可以通过扩展 JsonDeserializer ,并在里面处理XSS即可。

对于  PathVariableMethodArgumentResolver、 RequestParamMethodArgumentResolver、 ServletModelAttributeMethodProcessor ,在 resolveArgument() 方法中,他们对需要对请求参数调用 DataBinder 类 对获取到的参数类型转换和数据绑定。对于类型转换的过程,他们会使用 org.springframework.core.convert.converter.Converter  进行转换。同样,在SPRINGBOOT初始化的过程,也注册了很多个默认的转换器,我们可以注册一个自定义转换器,用于对数据进行xss处理。

3. 具体处理细节

抽象XSSCleaner,用于对string进行XSS处理

public interface XssCleaner {

    /**
     * 清理 html, 防止XSS
	 *
     * @param html html
     * @return 清理后的数据
     */
    String clean(String html);

}

使用JSOUP,做HTML节点的白名单处理

public class DefaultXssCleaner implements XssCleaner {

    public static final HtmlWhitelist WHITE_LIST = new HtmlWhitelist();

    @Override
    public String clean(String html) {
        if (StringUtils.hasText(html)) {
            return Jsoup.clean(html, WHITE_LIST);
        }
        return html;
    }

    private static class HtmlWhitelist extends Whitelist {

        public HtmlWhitelist() {
            //定义标签和属性的白名单

            addTags("a", "b", "blockquote", "br", "caption", "cite", "code", "col", "colgroup", "dd", "div", "span", "embed", "object", "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");

            addAttributes("a", "href", "title", "target");
            addAttributes("blockquote", "cite");
            addAttributes("col", "span");
            addAttributes("colgroup", "span");
            addAttributes("img", "align", "alt", "src", "title");
            addAttributes("ol", "start");
            addAttributes("q", "cite");
            addAttributes("table", "summary");
            addAttributes("td", "abbr", "axis", "colspan", "rowspan", "width");
            addAttributes("th", "abbr", "axis", "colspan", "rowspan", "scope", "width");
            addAttributes("video", "src", "autoplay", "controls", "loop", "muted", "poster", "preload");
            addAttributes("object", "width", "height", "classid", "codebase");
            addAttributes("param", "name", "value");
            addAttributes("embed", "src", "quality", "width", "height", "allowFullScreen", "allowScriptAccess", "flashvars", "name", "type", "pluginspage");

            addAttributes(":all", "class", "style", "height", "width", "type", "id", "name");

            addProtocols("blockquote", "cite", "http", "https");
            addProtocols("cite", "cite", "http", "https");
            addProtocols("q", "cite", "http", "https");

        }

        @Override
        protected boolean isSafeAttribute(String tagName, Element el, Attribute attr) {
            //不允许 javascript 开头的 src 和 href
            if ("src".equalsIgnoreCase(attr.getKey()) || "href".equalsIgnoreCase(attr.getKey())) {
                String value = attr.getValue();
                if (StringUtils.hasText(value) && value.toLowerCase().startsWith("javascript")) {
                    return false;
                }
            }

            //允许 base64 的图片内容
            if ("img".equals(tagName) && "src".equals(attr.getKey()) && attr.getValue().startsWith("data:;base64")) {
                return true;
            }

            return super.isSafeAttribute(tagName, el, attr);
        }
    }
}

新增一个jackson的JsonDeserializer 

/**
 * jackson的反序列化时的html xss过滤器
 */
public class JacksonXssCleanJsonDeserializer extends JsonDeserializer<String> {

    private final static Logger LOGGER = LoggerFactory.getLogger(JacksonXssCleanJsonDeserializer.class);

    private final XssCleaner xssCleaner;

    public JacksonXssCleanJsonDeserializer(XssCleaner xssCleaner) {
        this.xssCleaner = xssCleaner;
    }

    @Override
    public Class<?> handledType() {
        return String.class;
    }

    @Override
    public String deserialize(JsonParser p, DeserializationContext context) throws IOException, JsonProcessingException {
        // XSS clean
        String text = p.getValueAsString();
        if (StringUtils.hasText(text) && XssCleanMarker.shouldClean()) {
            String cleanText = xssCleaner.clean(text);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Json property value: [{}] cleaned up by JacksonXssCleanJsonDeserializer, current value is:{}.", text, cleanText);
            }
            return cleanText;
        }
        return text;
    }
}

新增 Converter 

/**
 * 对请求数据过滤xss
 */
public class XssCleanConverter implements Converter<String, String> {

    private final Logger LOGGER = LoggerFactory.getLogger(XssCleanConverter.class);

    private XssCleaner xssCleaner;

    public XssCleanConverter(XssCleaner xssCleaner) {
        this.xssCleaner = xssCleaner;
    }

    @Override
    public String convert(String text) {
        if (StringUtils.hasText(text) && XssCleanMarker.shouldClean()) {
            String cleanText = xssCleaner.clean(text);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("request param [{}] cleaned up by XssCleanConverter, current value is:{}.", text, cleanText);
            }
            return cleanText;
        }
        return text;
    }
}

对 JsonDeserializer  和 Converter 进行注册

@Configuration
@ConditionalOnProperty(value = XSS_PROPERTIES_ENABLED, havingValue = "true", matchIfMissing = true)
@ConditionalOnWebApplication
@EnableConfigurationProperties({XssProperties.class})
public class WebXssConfiguration implements WebMvcConfigurer {

    private XssProperties properties;

    public WebXssConfiguration(XssProperties properties) {
        this.properties = properties;
    }

    @Bean
    @ConditionalOnMissingBean
    public XssCleaner xssCleaner() {
        return new DefaultXssCleaner();
    }

    @Bean
    @ConditionalOnClass(value = {ObjectMapper.class, Jackson2ObjectMapperBuilder.class})
    public Jackson2ObjectMapperBuilderCustomizer jacksonXssCleanJsonDeserializerCustomer(XssCleaner xssCleaner) {
        return builder -> builder.deserializers(new JacksonXssCleanJsonDeserializer(xssCleaner));
    }

    @Override
    public void addFormatters(FormatterRegistry registry) {
        XssCleaner xssCleaner = xssCleaner();
        registry.addConverter(new XssCleanConverter(xssCleaner));
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        XssCleanMarkerHandlerInterceptor handlerInterceptor = new XssCleanMarkerHandlerInterceptor(properties);
        registry.addInterceptor(handlerInterceptor);
    }
}

上面是XSS处理的核心代码。处理XSS处理,还进行了一些扩展,比如 http path 路径的过滤 和 一些使能控制。

完整的代码可以参考仓库:仓库地址

以上就是springboot项目如何防止XSS攻击的详细内容,更多关于springboot防止XSS攻击的资料请关注脚本之家其它相关文章!

相关文章

  • Java生成随机数之Random与ThreadLocalRandom性能比较详解

    Java生成随机数之Random与ThreadLocalRandom性能比较详解

    大家项目中如果有生成随机数的需求,我想大多都会选择使用Random来实现,它内部使用了CAS来实现。 实际上,JDK1.7之后,提供了另外一个生成随机数的类ThreadLocalRandom,那么他们二者之间的性能是怎么样的呢?本文就来详细说说
    2022-12-12
  • SpringBoot扩展SpringMVC原理并实现全面接管

    SpringBoot扩展SpringMVC原理并实现全面接管

    这篇文章主要介绍了SpringBoot扩展SpringMVC原理并实现全面接管,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-11-11
  • 关于弗洛伊德算法求最短路径详解

    关于弗洛伊德算法求最短路径详解

    这篇文章主要介绍了关于弗洛伊德算法求最短路径详解,弗洛伊德算法VS迪杰斯特拉算法:迪杰斯特拉算法通过选定的被访问顶点,求出从出发访问顶点到其他项点的最短路径:弗洛伊德算法中每-个顶点都是出发访问点,需要的朋友可以参考下
    2023-07-07
  • Java连接mysql数据库的详细教程(推荐)

    Java连接mysql数据库的详细教程(推荐)

    这篇文章主要介绍了Java连接mysql数据库的详细教程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-08-08
  • 一文掌握Java中List和Set接口的基本使用

    一文掌握Java中List和Set接口的基本使用

    这篇文章主要为大家详细介绍Java中List和Set接口,文中通过示例代码让大家能够轻松掌握List和Set接口的基本使用,感兴趣的可以跟随小编一起学习一下
    2022-07-07
  • Maven中plugins和pluginManagement区别小结

    Maven中plugins和pluginManagement区别小结

    pluginManagement是表示插件声明,plugins就是直接引入一个plugin,本文主要介绍了Maven中plugins和pluginManagement区别小结,具有一定的参考价值,感兴趣的可以了解一下
    2024-06-06
  • 详解Spring Cloud微服务架构下的WebSocket解决方案

    详解Spring Cloud微服务架构下的WebSocket解决方案

    这篇文章主要介绍了详解Spring Cloud微服务架构下的WebSocket解决方案,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-12-12
  • Spring源码解析容器初始化构造方法

    Spring源码解析容器初始化构造方法

    这篇文章主要介绍了Spring源码解析容器初始化构造方法,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-07-07
  • Java9新特性Module模块化编程示例演绎

    Java9新特性Module模块化编程示例演绎

    这篇文章主要为大家介绍了Java9新特性Module模块化编程的示例演绎,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪
    2022-03-03
  • java读写excel文件实现POI解析Excel的方法

    java读写excel文件实现POI解析Excel的方法

    在日常工作中,我们常常会进行Excel文件读写操作,这篇文章主要介绍了java读写excel文件实现POI解析Excel的方法,实例分析了java读写excel的技巧,非常具有实用价值,需要的朋友可以参考下
    2018-10-10

最新评论