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 详解异常的处理机制

    Java 详解异常的处理机制

    异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。比如你的代码少一个分号,那运行出来结果是提示是错误 java.lang.Error;如果你用System.out.println(11/0),那你是因为用0做了除数,会抛出 java.lang.ArithmeticException 的异常
    2021-11-11
  • win10安装JDK14.0.2的详细安装过程

    win10安装JDK14.0.2的详细安装过程

    这篇文章主要介绍了win10安装JDK14.0.2的详细安装过程的相关资料,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09
  • SpringBoot静态方法调用Spring容器bean的三种解决方案

    SpringBoot静态方法调用Spring容器bean的三种解决方案

    在SpringBoot中静态方法调用Spring容器bean时出现的null值问题,本文就来介绍一下SpringBoot静态方法调用Spring容器bean的三种解决方案,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2025-01-01
  • 教你用Springboot实现拦截器获取header内容

    教你用Springboot实现拦截器获取header内容

    项目中遇到一个需求,对接上游系统是涉及到需要增加请求头,请求头的信息是动态获取的,需要动态从下游拿到之后转给上游,文中非常详细的介绍了该需求的实现,需要的朋友可以参考下
    2021-05-05
  • SpringBoot获取maven打包时间的两种方式

    SpringBoot获取maven打包时间的两种方式

    这篇文章主要介绍了SpringBoot获取maven打包时间的两种方式,文章通过代码示例给大家讲解的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下
    2024-05-05
  • SpringBoot之LogBack配置详解

    SpringBoot之LogBack配置详解

    这篇文章主要介绍了SpringBoot之LogBack配置详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-02-02
  • 详解Spring如何扫描自定义的注解

    详解Spring如何扫描自定义的注解

    本文给大家详细介绍了Spring如何扫描自定义的注解,在Spring中,可以使用注解来实现依赖注入、AOP等功能,同时,Spring也支持自定义注解,使得开发人员可以更灵活地使用注解,需要的朋友可以参考下
    2024-02-02
  • Spring-IOC容器-Bean管理-基于XML方式超详解

    Spring-IOC容器-Bean管理-基于XML方式超详解

    这篇文章主要介绍了Spring为IOC容器Bean的管理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2021-08-08
  • spring boot mybatis枚举映射示例代码

    spring boot mybatis枚举映射示例代码

    这篇文章主要给大家介绍了关于spring boot mybatis枚举映射的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Spring Boot具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-09-09
  • Java基础详解之内存泄漏

    Java基础详解之内存泄漏

    这篇文章主要介绍了Java基础详解之内存泄漏,文中有非常详细的代码示例,对正在学习java的小伙伴们有很好地帮助,需要的朋友可以参考下
    2021-04-04

最新评论