Preventing XSS Attacks in your Spring Boot API
XSS (Cross-Site Scripting) attacks pose a significant threat to web applications. In this tutorial, you’ll learn how to implement security measures to prevent these attacks in your Spring Boot-developed API.
Add OWASP dependency and Spring Security
<dependency>
<groupId>org.owasp.encoder</groupId>
<artifactId>encoder</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
An average application might lack input data prevention measures or have minimal measures in place. Here’s a basic example of a Spring Boot application without XSS security measures.
For parameters:
For body request:
{
"id": 1,
"name": "<script>alert('Hola >:)!')</script>"
}
Step 1: Create the XSS Protection Filter
The XSSFilter
intercepts incoming requests and applies the necessary sanitization to prevent XSS attacks.
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter("/*")
public class XSSFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(new XSSRequestWrapper((HttpServletRequest) request), response);
}
}
Create a Wrapper
public class XSSRequestWrapper extends HttpServletRequestWrapper {
public XSSRequestWrapper(HttpServletRequest request) {
super(request);
}
@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] = sanitizeInput(values[i]);
}
return encodedValues;
}
private String sanitizeInput(String string) {
return Encode.forHtml(string);
}
}
In the example of the XSSRequestWrapper
, it's used to intercept HTTP requests before they are fully processed by the server. The wrapper extends the functionality of the HttpServletRequestWrapper
class, which is an implementation of HttpServletRequest
provided by the server. This wrapper overrides certain methods, such as getParameter()
and getReader()
, to apply sanitization logic to the data entering the HTTP request.
The primary function of the wrapper is to provide an additional layer of processing to HTTP requests. In the case of the XSS filter, it’s used to sanitize input data and prevent potential XSS attacks. This is achieved by examining and modifying the parameters and body of the request before they are processed and passed to the main controller or application.
Step 2: Configure XSS Protection Filter
@Configuration
public class WebConfig {
@Bean
public FilterRegistrationBean<XSSFilter> filterRegistrationBean() {
FilterRegistrationBean<XSSFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new XSSFilter());
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}
This snippet demonstrates the setup of an XSSFilter within a Spring Boot application. The filter intercepts all incoming requests ("/*"
) and applies the XSSFilter logic, which includes sanitization procedures to prevent Cross-Site Scripting attacks.
Step 3: Configure XSS Protection in Spring Security
We’ll add response headers to enhance security against XSS attacks:
@Configuration
public class SecurityConfig {
@Bean
SecurityFilterChain config(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(
re -> re.anyRequest().permitAll()
)
.headers(
header ->
header
.xssProtection()
.and()
.contentSecurityPolicy(cs -> cs.policyDirectives("script-src 'self'"))
);
return http.build();
}
}
Step 4: Update Wrapper for sanitize Json
import com.fasterxml.jackson.databind.ObjectMapper;
// ...
public class XSSRequestWrapper extends HttpServletRequestWrapper {
private final ObjectMapper objectMapper = new ObjectMapper();
// ... (other methods)
@Override
public ServletInputStream getInputStream() throws IOException {
ServletInputStream originalInputStream = super.getInputStream();
String requestBody = new String(originalInputStream.readAllBytes());
// Sanitize the JSON body
String sanitizedBody = sanitizeInput(requestBody);
return new ServletInputStream() {
private final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(
sanitizedBody.getBytes()
);
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
@Override
public boolean isFinished() {
return byteArrayInputStream.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
// ... (other methods)
private String sanitizeInput(String input) {
return Encode.forHtml(input); // Implement appropriate sanitization logic
}
}
This modified XSSRequestWrapper
intercepts the JSON request body and applies sanitization before sending it to the servlet for processing. This ensures that the received JSON data is also treated to prevent potential XSS attacks.
Others samples using controllers
@RestController
@RequestMapping("/api")
public class DemoController {
@PostMapping("/")
public Object test(@RequestBody Object re) throws JsonProcessingException {
//Prevent with Xss filter for body request
System.out.println(new ObjectMapper().writeValueAsString(re));
return re;
}
@GetMapping("/test-unescape")
public String testUn(@RequestParam String datos) {
//Prevent with Xss filter for parameters
System.out.println(datos);
return datos;
}
@GetMapping("/test")
public String testResturn() {
//return a execution script if spring security configuration is disable
return """
{
"id": 1,
"name": "<script>alert('Hola >:)!')</script>"
}
""";
}
}
Result
Conclusion
Here are some of the most common ways XSS attacks can enter web applications, and while we’ve covered a great deal, there’s much more to consider if needed. I hope this helps in making your application more secure.”
XSS attacks can take various forms and it’s crucial to consider a wide range of attack vectors to ensure comprehensive protection. Some common forms of XSS attacks include:
- Reflected: User-entered data is directly reflected in the server’s response, such as through URL parameters or customized error messages.
- Stored: Malicious data is stored in databases or other data stores and later displayed to other users. This can happen in comment sections, user profiles, etc.
- DOM-based: Occurs on the client side when insecure JavaScript code manipulates the DOM using unvalidated data.
- Third-party attacks: Third-party scripts, like embedded ads or widgets, can be manipulated to execute malicious code.
- Form-based attacks: Input fields, such as web forms, can be exploited to inject malicious code.
- HTTP Headers: HTTP headers can be manipulated to inject scripts.
- Cookie-based attacks: Cookie manipulation can also be a form of XSS attack.
While we’ve covered a significant portion of these vulnerabilities, it’s always advisable to continuously review and enhance security measures, such as input data sanitization, implementing security headers, using Content Security Policy (CSP), and educating end-users about security risks.
These steps are crucial in strengthening a web application’s security and preventing XSS attacks! If you need more details on specific areas or how to further extend security, I’m here to help