经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring » 查看文章
SpringBoot2.x 整合 AntiSamy防御XSS攻击的简单总结
来源:jb51  时间:2021/8/26 12:37:44  对本文有异议

AntiSamy是OWASP的一个开源项目,通过对用户输入的HTML、CSS、JavaScript等内容进行检验和清理,确保输入符合应用规范。AntiSamy被广泛应用于Web服务对存储型和反射型XSS的防御中。

XSS攻击全称为跨站脚本攻击(Cross Site Scripting),是一种在web应用中的计算机安全漏洞,它允许用户将恶意代码(如script脚本)植入到Web页面中,为了不和层叠样式表(Cascading Style Sheets, CSS)混淆,一般缩写为XSS。XSS分为以下两种类型:

  • 存储型XSS:服务端对用户输入的恶意脚本没有经过验证就存入数据库,每次调用数据库都会将其渲染在浏览器上。则可能为存储型XSS。
  • 反射型XSS:通过get或者post等方式,向服务端输入数据。如果服务端不进行过滤,验证或编码,直接将用户信息呈现出来,可能会造成反射型XSS。

本文主要对SpringBoot2.x集成AntiSamy防御XSS攻击进行简单总结,其中SpringBoot使用的2.4.5版本。

一、引入依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. </dependency>
  5. <!-- AntiSamy依赖 -->
  6. <dependency>
  7. <groupId>org.owasp.antisamy</groupId>
  8. <artifactId>antisamy</artifactId>
  9. <version>1.6.2</version>
  10. </dependency>
  11. <!-- lombok插件 -->
  12. <dependency>
  13. <groupId>org.projectlombok</groupId>
  14. <artifactId>lombok</artifactId>
  15. <version>1.18.8</version>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.apache.commons</groupId>
  19. <artifactId>commons-text</artifactId>
  20. <version>1.9</version>
  21. </dependency>

二、策略文件

Antisamy对恶意代码的过滤依赖于策略文件,策略文件为xml格式,规定了AntiSamy对各个标签、属性的处理方法。策略文件定义的严格与否,决定了AntiSamy对Xss的防御效果。在AntiSamy的jar包中,已经包含了几个常用的策略文件:

1

本文使用antisamy-ebay.xml作为策略文件,该策略相对安全,适用于电商网站。将antisamy-ebay.xmlantisamy.xsd复制到resouces目录下。对于策略文件的具体内容这里不进行深入了解,只需了解下对标签的处理规则<tag-rules>,共有remove、truncate、validate三种处理方式,其中remove为直接删除,truncate为缩短标签,只保留标签和值,validate为验证标签属性:

2

上图截取了<tag-rules>的一部分,可知对script标签的处理策略是remove。

三、实体类和Controller

用户实体类:

  1. package com.rtxtitanv.model;
  2.  
  3. import lombok.AllArgsConstructor;
  4. import lombok.Data;
  5. import lombok.NoArgsConstructor;
  6.  
  7. /**
  8. * @author rtxtitanv
  9. * @version 1.0.0
  10. * @name com.rtxtitanv.model.User
  11. * @description 用户实体类
  12. * @date 2021/8/23 14:54
  13. */
  14. @AllArgsConstructor
  15. @NoArgsConstructor
  16. @Data
  17. public class User {
  18. private Long id;
  19. private String username;
  20. private String password;
  21. }

Controller:

  1. package com.rtxtitanv.controller;
  2.  
  3. import com.rtxtitanv.model.User;
  4. import org.springframework.web.bind.annotation.*;
  5.  
  6. /**
  7. * @author rtxtitanv
  8. * @version 1.0.0
  9. * @name com.rtxtitanv.controller.UserController
  10. * @description UserController
  11. * @date 2021/8/23 14:54
  12. */
  13. @RequestMapping("/user")
  14. @RestController
  15. public class UserController {
  16.  
  17. @PostMapping("/save")
  18. public User saveUser(User user) {
  19. return user;
  20. }
  21.  
  22. @GetMapping("/get")
  23. public User getUserById(@RequestParam(value = "id") Long id) {
  24. return new User(id, "ZhaoYun", "123456");
  25. }
  26.  
  27. @PutMapping("/update")
  28. public User updateUser(@RequestBody User user) {
  29. return user;
  30. }
  31. }

四、创建过滤器

  1. package com.rtxtitanv.filter;
  2.  
  3. import com.rtxtitanv.wrapper.XssRequestWrapper;
  4.  
  5. import javax.servlet.*;
  6. import javax.servlet.http.HttpServletRequest;
  7. import java.io.IOException;
  8.  
  9. /**
  10. * @author rtxtitanv
  11. * @version 1.0.0
  12. * @name com.rtxtitanv.filter.XssFilter
  13. * @description XSS过滤器
  14. * @date 2021/8/23 15:01
  15. */
  16. public class XssFilter implements Filter {
  17.  
  18. private FilterConfig filterConfig;
  19.  
  20. @Override
  21. public void init(FilterConfig filterConfig) throws ServletException {
  22. this.filterConfig = filterConfig;
  23. }
  24.  
  25. @Override
  26. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
  27. throws IOException, ServletException {
  28. // 拦截请求,处理XSS过滤
  29. chain.doFilter(new XssRequestWrapper((HttpServletRequest)request), response);
  30. }
  31.  
  32. @Override
  33. public void destroy() {
  34. this.filterConfig = null;
  35. }
  36. }

注意:在过滤器中并没有直接对请求参数进行过滤清洗,而是在XssRequestWrapper类中进行的。XssRequestWrapper类将当前的request对象进行了包装,在过滤器放行时会自动调用XssRequestWrapper中的方法对请求参数进行清洗。

五、创建XssRequestWrapper类

  1. package com.rtxtitanv.wrapper;
  2.  
  3. import com.fasterxml.jackson.core.JsonGenerator;
  4. import com.fasterxml.jackson.databind.JsonSerializer;
  5. import com.fasterxml.jackson.databind.SerializerProvider;
  6. import org.apache.commons.lang3.StringUtils;
  7. import org.apache.commons.text.StringEscapeUtils;
  8. import org.owasp.validator.html.*;
  9. import org.slf4j.Logger;
  10. import org.slf4j.LoggerFactory;
  11.  
  12. import javax.servlet.http.HttpServletRequest;
  13. import javax.servlet.http.HttpServletRequestWrapper;
  14. import java.io.IOException;
  15. import java.io.UnsupportedEncodingException;
  16. import java.net.URLDecoder;
  17. import java.util.Map;
  18. import java.util.Objects;
  19.  
  20. /**
  21. * @author rtxtitanv
  22. * @version 1.0.0
  23. * @name com.rtxtitanv.wrapper.XssRequestWrapper
  24. * @description 装饰器模式加强对request的处理,基于AntiSamy进行XSS防御
  25. * @date 2021/8/23 15:01
  26. */
  27. public class XssRequestWrapper extends HttpServletRequestWrapper {
  28.  
  29. private static final Logger LOGGER = LoggerFactory.getLogger(XssRequestWrapper.class);
  30. private static Policy policy = null;
  31.  
  32. static {
  33. try {
  34. // 获取策略文件路径,策略文件需要放到项目的classpath下
  35. String antiSamyPath = Objects
  36. .requireNonNull(XssRequestWrapper.class.getClassLoader().getResource("antisamy-ebay.xml")).getFile();
  37. LOGGER.info(antiSamyPath);
  38. // 获取的文件路径中有空格时,空格会被替换为%20,在new一个File对象时会出现找不到路径的错误
  39. // 对路径进行解码以解决该问题
  40. antiSamyPath = URLDecoder.decode(antiSamyPath, "utf-8");
  41. LOGGER.info(antiSamyPath);
  42. // 指定策略文件
  43. policy = Policy.getInstance(antiSamyPath);
  44. } catch (UnsupportedEncodingException | PolicyException e) {
  45. e.printStackTrace();
  46. }
  47. }
  48.  
  49. public XssRequestWrapper(HttpServletRequest request) {
  50. super(request);
  51. }
  52.  
  53. /**
  54. * 过滤请求头
  55. *
  56. * @param name 参数名
  57. * @return 参数值
  58. */
  59. @Override
  60. public String getHeader(String name) {
  61. String header = super.getHeader(name);
  62. // 如果Header为空,则直接返回,否则进行清洗
  63. return StringUtils.isBlank(header) ? header : xssClean(header);
  64. }
  65.  
  66. /**
  67. * 过滤请求参数
  68. *
  69. * @param name 参数名
  70. * @return 参数值
  71. */
  72. @Override
  73. public String getParameter(String name) {
  74. String parameter = super.getParameter(name);
  75. // 如果Parameter为空,则直接返回,否则进行清洗
  76. return StringUtils.isBlank(parameter) ? parameter : xssClean(parameter);
  77. }
  78.  
  79. /**
  80. * 过滤请求参数(一个参数可以有多个值)
  81. *
  82. * @param name 参数名
  83. * @return 参数值数组
  84. */
  85. @Override
  86. public String[] getParameterValues(String name) {
  87. String[] parameterValues = super.getParameterValues(name);
  88. if (parameterValues != null) {
  89. int length = parameterValues.length;
  90. String[] newParameterValues = new String[length];
  91. for (int i = 0; i < length; i++) {
  92. LOGGER.info("AntiSamy清理之前的参数值:" + parameterValues[i]);
  93. // 清洗参数
  94. newParameterValues[i] = xssClean(parameterValues[i]);
  95. LOGGER.info("AntiSamy清理之后的参数值:" + newParameterValues[i]);
  96. }
  97. return newParameterValues;
  98. }
  99. return super.getParameterValues(name);
  100. }
  101.  
  102. @Override
  103. public Map<String, String[]> getParameterMap() {
  104. Map<String, String[]> requestMap = super.getParameterMap();
  105. requestMap.forEach((key, value) -> {
  106. for (int i = 0; i < value.length; i++) {
  107. LOGGER.info(value[i]);
  108. value[i] = xssClean(value[i]);
  109. LOGGER.info(value[i]);
  110. }
  111. });
  112. return requestMap;
  113. }
  114.  
  115. /**
  116. * 使用AntiSamy清洗数据
  117. *
  118. * @param value 需要清洗的数据
  119. * @return 清洗后的数据
  120. */
  121. private String xssClean(String value) {
  122. try {
  123. AntiSamy antiSamy = new AntiSamy();
  124. // 使用AntiSamy清洗数据
  125. final CleanResults cleanResults = antiSamy.scan(value, policy);
  126. // 获得安全的HTML输出
  127. value = cleanResults.getCleanHTML();
  128. // 对转义的HTML特殊字符(<、>、"等)进行反转义,因为AntiSamy调用scan方法时会将特殊字符转义
  129. return StringEscapeUtils.unescapeHtml4(value);
  130. } catch (ScanException | PolicyException e) {
  131. e.printStackTrace();
  132. }
  133. return value;
  134. }
  135.  
  136. /**
  137. * 通过修改Json序列化的方式来完成Json格式的XSS过滤
  138. */
  139. public static class XssStringJsonSerializer extends JsonSerializer<String> {
  140.  
  141. @Override
  142. public Class<String> handledType() {
  143. return String.class;
  144. }
  145.  
  146. @Override
  147. public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
  148. if (!StringUtils.isBlank(value)) {
  149. try {
  150. AntiSamy antiSamy = new AntiSamy();
  151. final CleanResults cleanResults = antiSamy.scan(value, XssRequestWrapper.policy);
  152. gen.writeString(StringEscapeUtils.unescapeHtml4(cleanResults.getCleanHTML()));
  153. } catch (ScanException | PolicyException e) {
  154. e.printStackTrace();
  155. }
  156. }
  157. }
  158. }
  159. }

六、创建配置类

  1. package com.rtxtitanv.config;
  2.  
  3. import com.fasterxml.jackson.databind.ObjectMapper;
  4. import com.fasterxml.jackson.databind.module.SimpleModule;
  5. import com.rtxtitanv.filter.XssFilter;
  6. import com.rtxtitanv.wrapper.XssRequestWrapper;
  7. import org.springframework.boot.web.servlet.FilterRegistrationBean;
  8. import org.springframework.context.annotation.Bean;
  9. import org.springframework.context.annotation.Configuration;
  10. import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
  11.  
  12. import javax.servlet.Filter;
  13.  
  14. /**
  15. * @author rtxtitanv
  16. * @version 1.0.0
  17. * @name com.rtxtitanv.config.AntiSamyConfig
  18. * @description AntiSamy配置类
  19. * @date 2021/8/23 15:05
  20. */
  21. @Configuration
  22. public class AntiSamyConfig {
  23.  
  24. /**
  25. * 配置XSS过滤器
  26. *
  27. * @return FilterRegistrationBean
  28. */
  29. @Bean
  30. public FilterRegistrationBean<Filter> filterRegistrationBean() {
  31. FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>(new XssFilter());
  32. filterRegistrationBean.addUrlPatterns("/*");
  33. filterRegistrationBean.setOrder(1);
  34. return filterRegistrationBean;
  35. }
  36.  
  37. /**
  38. * 用于过滤Json类型数据的解析器
  39. *
  40. * @param builder Jackson2ObjectMapperBuilder
  41. * @return ObjectMapper
  42. */
  43. @Bean
  44. public ObjectMapper xssObjectMapper(Jackson2ObjectMapperBuilder builder) {
  45. // 创建解析器
  46. ObjectMapper objectMapper = builder.createXmlMapper(false).build();
  47. // 注册解析器
  48. SimpleModule simpleModule = new SimpleModule("XssStringJsonSerializer");
  49. simpleModule.addSerializer(new XssRequestWrapper.XssStringJsonSerializer());
  50. objectMapper.registerModule(simpleModule);
  51. return objectMapper;
  52. }
  53. }

七、测试

启动项目,发送如下POST请求,请求地址为http://localhost:8080/user/save,可见表单参数中的<script>标签内容被成功过滤:

3

发送如下GET请求,请求地址为http://localhost:8080/user/get?id=1<script>alert("XSS");</script>0,可见Query参数中的<script>标签内容被成功过滤:

4

发送如下PUT请求,请求地址为http://localhost:8080/user/update,可见Json类型参数中的<script>标签内容被成功过滤:

5

代码示例

Github:https://github.com/RtxTitanV/springboot-learning/tree/master/springboot2.x-learning/springboot-antisamy

Gitee:https://gitee.com/RtxTitanV/springboot-learning/tree/master/springboot2.x-learning/springboot-antisamy

到此这篇关于SpringBoot2.x 整合 AntiSamy防御XSS攻击的简单总结的文章就介绍到这了,更多相关SpringBoot2.x防御XSS攻击内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号