Cross-Site Request Forgery (CSRF) Due to Disabled CSRF Protection in Spring Security

High Risk Cross-Site Request Forgery
JavaSpring SecurityCSRFSession ManagementWeb SecurityAuthentication Bypass

What it is

Applications that disable CSRF protection in Spring Security configuration are vulnerable to Cross-Site Request Forgery attacks, where attackers can trick authenticated users into performing unintended actions by exploiting their active session cookies.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class VulnerableSecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
            .csrf().disable() // Vulnerable: Disables CSRF protection
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/admin/**").authenticated()
                .requestMatchers("/api/**").authenticated()
                .anyRequest().permitAll()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .defaultSuccessUrl("/dashboard")
            )
            .logout(logout -> logout
                .logoutUrl("/logout")
                .logoutSuccessUrl("/")
            )
            .build();
    }
}

@RestController
public class VulnerableController {
    
    @Autowired
    private UserService userService;
    
    @Autowired
    private BankService bankService;
    
    // Vulnerable: No CSRF protection for state-changing operations
    @PostMapping("/api/user/update-email")
    public ResponseEntity<String> updateEmail(
            @RequestParam String newEmail,
            Authentication auth) {
        
        String username = auth.getName();
        userService.updateEmail(username, newEmail);
        
        return ResponseEntity.ok("Email updated successfully");
    }
    
    // Vulnerable: Money transfer without CSRF protection
    @PostMapping("/api/transfer")
    public ResponseEntity<String> transferMoney(
            @RequestParam String toAccount,
            @RequestParam BigDecimal amount,
            Authentication auth) {
        
        String fromAccount = auth.getName();
        bankService.transfer(fromAccount, toAccount, amount);
        
        return ResponseEntity.ok("Transfer completed");
    }
    
    // Vulnerable: Admin operations without CSRF protection
    @PostMapping("/admin/users/delete")
    public ResponseEntity<String> deleteUser(
            @RequestParam String userId,
            Authentication auth) {
        
        userService.deleteUser(userId);
        return ResponseEntity.ok("User deleted");
    }
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;

@Configuration
@EnableWebSecurity
public class SecureSecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
            // CSRF protection enabled by default
            .csrf(csrf -> csrf
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                // Allow JavaScript to read CSRF token
            )
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/admin/**").authenticated()
                .requestMatchers("/api/**").authenticated()
                .anyRequest().permitAll()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .defaultSuccessUrl("/dashboard")
            )
            .logout(logout -> logout
                .logoutUrl("/logout")
                .logoutSuccessUrl("/")
            )
            .build();
    }
}

@RestController
public class SecureController {
    
    @Autowired
    private UserService userService;
    
    @Autowired
    private BankService bankService;
    
    // Secure: CSRF protection automatically applied
    @PostMapping("/api/user/update-email")
    public ResponseEntity<String> updateEmail(
            @RequestParam String newEmail,
            Authentication auth) {
        
        // Validate input
        if (!EmailValidator.isValid(newEmail)) {
            return ResponseEntity.badRequest().body("Invalid email format");
        }
        
        String username = auth.getName();
        userService.updateEmail(username, newEmail);
        
        return ResponseEntity.ok("Email updated successfully");
    }
    
    // Secure: CSRF token required for money transfer
    @PostMapping("/api/transfer")
    public ResponseEntity<String> transferMoney(
            @Valid @RequestBody TransferRequest request,
            Authentication auth) {
        
        // Additional validation
        if (request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
            return ResponseEntity.badRequest().body("Invalid transfer amount");
        }
        
        String fromAccount = auth.getName();
        
        // Check if user has permission to transfer from account
        if (!bankService.canTransferFrom(fromAccount, fromAccount)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN)
                .body("Not authorized to transfer from this account");
        }
        
        try {
            bankService.transfer(fromAccount, request.getToAccount(), request.getAmount());
            return ResponseEntity.ok("Transfer completed");
        } catch (InsufficientFundsException e) {
            return ResponseEntity.badRequest().body("Insufficient funds");
        }
    }
    
    // Secure: Admin operations protected by CSRF
    @PostMapping("/admin/users/delete")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<String> deleteUser(
            @RequestParam String userId,
            Authentication auth) {
        
        // Additional authorization check
        if (!adminService.canDeleteUser(auth.getName(), userId)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN)
                .body("Not authorized to delete this user");
        }
        
        userService.deleteUser(userId);
        
        // Log the admin action
        auditService.logUserDeletion(auth.getName(), userId);
        
        return ResponseEntity.ok("User deleted");
    }
    
    // Provide CSRF token to frontend
    @GetMapping("/api/csrf-token")
    public CsrfToken csrfToken(CsrfToken token) {
        return token;
    }
}

// DTO for transfer request with validation
public class TransferRequest {
    @NotBlank(message = "Destination account is required")
    @Pattern(regexp = "[0-9]{10,20}", message = "Invalid account number format")
    private String toAccount;
    
    @NotNull(message = "Amount is required")
    @DecimalMin(value = "0.01", message = "Amount must be positive")
    @DecimalMax(value = "999999.99", message = "Amount exceeds maximum")
    private BigDecimal amount;
    
    // Getters and setters
    public String getToAccount() { return toAccount; }
    public void setToAccount(String toAccount) { this.toAccount = toAccount; }
    public BigDecimal getAmount() { return amount; }
    public void setAmount(BigDecimal amount) { this.amount = amount; }
}

💡 Why This Fix Works

The vulnerable version disables CSRF protection entirely, making the application susceptible to CSRF attacks. The secure version enables CSRF protection with cookie-based token repository and includes proper validation and authorization checks.

â„šī¸ Configuration Fix

Configuration changes required - see explanation below.

💡 Explanation

This example shows how attackers can exploit CSRF vulnerabilities by creating malicious web pages that automatically submit requests to vulnerable applications using the victim's authenticated session.

â„šī¸ Configuration Fix

Configuration changes required - see explanation below.

💡 Explanation

This secure frontend implementation properly handles CSRF tokens by reading them from cookies or API endpoints and including them in all state-changing requests.

Why it happens

Security configurations that explicitly disable CSRF protection using csrf().disable(), removing token validation for state-changing operations.

Root causes

Explicitly Disabled CSRF Protection

Security configurations that explicitly disable CSRF protection using csrf().disable(), removing token validation for state-changing operations.

Preview example – JAVA
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
            .csrf().disable() // Vulnerable: Disables CSRF protection
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/**").authenticated()
                .anyRequest().permitAll()
            )
            .build();
    }
}

Conditional CSRF Disabling

Applications that disable CSRF protection for certain endpoints or in development mode but forget to re-enable it in production.

Preview example – JAVA
@Configuration
public class SecurityConfig {
    
    @Value("${app.environment}")
    private String environment;
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        HttpSecurity config = http.authorizeHttpRequests(auth -> auth
            .requestMatchers("/admin/**").authenticated()
            .anyRequest().permitAll()
        );
        
        // Vulnerable: May disable CSRF in production
        if ("development".equals(environment)) {
            config.csrf().disable();
        }
        
        return config.build();
    }
}

API-First Applications Without Proper CSRF Configuration

REST API applications that disable CSRF globally without implementing proper stateless authentication or token-based security.

Preview example – JAVA
@Configuration
@EnableWebSecurity
public class ApiSecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
            .csrf().disable() // Disabled for "REST API"
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .httpBasic() // Still uses sessions in some cases
            .and()
            .build();
    }
}

Fixes

1

Enable Default CSRF Protection

Remove csrf().disable() to use Spring Security's default CSRF protection, which generates and validates CSRF tokens for unsafe HTTP methods.

2

Configure Cookie-Based CSRF Repository

Use CookieCsrfTokenRepository for JavaScript applications that need to include CSRF tokens in AJAX requests.

3

Implement Custom CSRF Configuration

Configure CSRF protection with custom token repository and matchers for specific use cases while maintaining security.

4

Implement Stateless Authentication for APIs

For true stateless APIs, implement JWT-based authentication and disable CSRF only for specific stateless endpoints.

Detect This Vulnerability in Your Code

Sourcery automatically identifies cross-site request forgery (csrf) due to disabled csrf protection in spring security and many other security issues in your codebase.