XSS (Cross-Site Scripting) in Spring Security

XSS is a critical security vulnerability where attackers inject malicious scripts into web pages viewed by other users. Here's how Spring Security helps protect against it:

Default XSS Protections in Spring Security

Spring Security provides several built-in protections:

1. X-XSS-Protection Header

Spring Security automatically adds security headers including:

X-XSS-Protection: 1; mode=block

2. Content Security Policy (CSP)

You can configure CSP headers to restrict script sources:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .headers(headers -> headers
                .contentSecurityPolicy(csp -> csp
                    .policyDirectives("script-src 'self'")
                )
            );
        return http.build();
    }
}

Common XSS Vulnerabilities in Spring Boot

1. Thymeleaf Template Injection

Vulnerable Code:

@GetMapping("/greet")
public String greet(@RequestParam String name, Model model) {
    model.addAttribute("name", name); // Unescaped
    return "greeting";
}

Safe Approach:

<!-- Thymeleaf automatically escapes by default -->
<p th:text="${name}">Name</p>
<!-- For unescaped HTML (use cautiously) -->
<p th:utext="${trustedHtml}">HTML</p>

2. JSON Response XSS

Vulnerable:

@GetMapping("/api/user")
@ResponseBody
public String getUser(@RequestParam String name) {
    return "{\"name\": \"" + name + "\"}"; // Direct concatenation
}

Safe:

@GetMapping("/api/user")
@ResponseBody
public User getUser(@RequestParam String name) {
    return new User(name); // Jackson automatically encodes
}

Best Practices for XSS Prevention

1. Input Validation

import jakarta.validation.constraints.Pattern;

public class UserDTO {
    @Pattern(regexp = "^[a-zA-Z0-9\\s]+$", 
             message = "Name contains invalid characters")
    private String name;
}

2. Output Encoding

import org.springframework.web.util.HtmlUtils;

@Service
public class UserService {
    public String sanitizeInput(String input) {
        return HtmlUtils.htmlEscape(input);
    }
}

3. CSRF Protection (related to XSS)

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            );
        return http.build();
    }
}

4. Custom XSS Filter

import org.jsoup.Jsoup;
import org.jsoup.safety.Safelist;

public class XSSUtils {
    public static String sanitize(String input) {
        if (input == null) return null;
        return Jsoup.clean(input, Safelist.basic());
    }
}
// Usage in Controller
@PostMapping("/comment")
public String addComment(@RequestParam String comment) {
    String sanitized = XSSUtils.sanitize(comment);
    // Save sanitized comment
    return "success";
}

5. Security Headers Configuration

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .headers(headers -> headers
            .xssProtection(xss -> xss.headerValue(
                XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK
            ))
            .contentSecurityPolicy(csp -> csp
                .policyDirectives("default-src 'self'; " +
                                "script-src 'self' 'unsafe-inline'; " +
                                "style-src 'self' 'unsafe-inline';")
            )
        );
    return http.build();
}

Testing for XSS

@Test
public void testXSSProtection() throws Exception {
    String maliciousInput = "<script>alert('XSS')</script>";
    
    mockMvc.perform(get("/greet")
            .param("name", maliciousInput))
            .andExpect(status().isOk())
            .andExpect(content().string(
                not(containsString("<script>"))
            ));
}

Key Takeaways

  1. Never trust user input — Always validate and sanitize
  2. Use template engines properly — Thymeleaf escapes by default
  3. Enable security headers — Spring Security provides these out-of-the-box
  4. Use CSP headers — Restrict where scripts can be loaded from
  5. Encode output — Use HtmlUtils or Jsoup for sanitization
  6. Keep dependencies updated — Security patches are regularly released