Back to Web Vulnerabilities: SQLi, XSS, CSRF, SSRF
Q2Question 2 of 7

CSRF (Cross-Site Request Forgery) and How to Prevent It

Key Concepts
  • CSRF tricks a logged-in user's browser into sending an unwanted, state-changing request to a site where the user is already authenticated.
  • It works because browsers automatically attach cookies to any request to a domain, regardless of which site initiated it.
  • The synchronizer token pattern embeds an unguessable anti-CSRF token in forms that the attacker cannot read or guess.
  • The SameSite cookie attribute (Strict or Lax) tells the browser not to attach cookies on cross-site requests.
  • Token-based APIs with Authorization headers are not CSRF-prone because browsers do not auto-attach custom headers.
  • Spring Security enables CSRF protection by default for session-based applications.

Definition

CSRF rides the victim's auto-sent cookie; stop it with an unguessable token and SameSite cookies.
  • CSRF (Cross-Site Request Forgery) – an attack that tricks a victim's browser into sending an unwanted, state-changing request to a site where the victim is already authenticated.
  • Root cause – the browser automatically attaches cookies to any request to a domain, regardless of which site initiated it.
  • Synchronizer token – a secret, per-session anti-CSRF token embedded in forms or headers that the attacker cannot read or guess.
  • SameSite attribute – a cookie attribute (Strict or Lax) that stops the browser from sending the cookie on cross-site requests.
  • Not affected – APIs that use Authorization: Bearer headers instead of cookies, because browsers do not auto-attach custom headers.

Rajesh is logged into National Bank of India in one tab. In another tab he visits a malicious site that secretly contains a hidden form which auto-submits a transfer request to the bank. Because the browser automatically attaches Rajesh's bank cookie, the bank sees a seemingly valid transfer instruction and obeys it. A CSRF token is like the bank requiring a secret one-time stamp that only its own genuine pages print; the malicious site cannot know the stamp, so its forged instruction is rejected.

Understanding the Concepts

What is CSRF?

Cross-Site Request Forgery (CSRF) is an attack where a malicious site tricks a victim's browser into sending an unwanted request to a site where the victim is already authenticated. The browser automatically attaches the victim's session cookie, so the server believes the request is legitimate.

The attacker never reads the response. The damage is the side effect: money transferred, email changed, password reset.

HTML
<!-- Malicious page on evil.com --syn-kw">>
syn-kw"><h1syn-kw">>You won a prize! Click here!syn-kw"></h1syn-kw">>
syn-kw"><form action="https://nationalbank.in/api/transfer" method="POST" id="attack"syn-kw">>
    syn-kw"><input type="hidden" name="toAccount" value="ATTACKER_ACCT" /syn-kw">>
    syn-kw"><input type="hidden" name="amount" value="50000" /syn-kw">>
syn-kw"></formsyn-kw">>
syn-kw"><scriptsyn-kw">>document.getElementById('attack').submit();syn-kw"></scriptsyn-kw">>
TEXT
What happens:
1. Rajesh visits evil.com (in a new tab)
2. evil.com auto-submits a form to nationalbank.in
3. Browser attaches Rajesh's session cookie automatically
4. Bank server sees valid cookie --> executes transfer to attacker!
5. Rajesh loses 50,000 INR without clicking anything

How does the synchronizer token pattern prevent CSRF?

The server generates a random anti-CSRF token, stores it in the user's session, and embeds it in every form as a hidden field. On submission, the server verifies the token matches.

The attacker's cross-site page cannot read this token because the same-origin policy blocks reading the victim's pages from another domain.

JAVA
package com.bank.security;

import jakarta.servlet.http.HttpSession;
import java.security.SecureRandom;
import java.util.Base64;

public class CsrfTokenService {

    public String generateToken(HttpSession session) {
        byte[] bytes = new byte[32];
        new SecureRandom().nextBytes(bytes);
        String token = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
        session.setAttribute("csrf_token", token);
        return token;
    }

    public boolean validateToken(HttpSession session, String submittedToken) {
        String stored = (String) session.getAttribute("csrf_token");
        if (stored == null || !stored.equals(submittedToken)) {
            System.out.println("CSRF token mismatch. Request rejected.");
            return false;
        }
        System.out.println("CSRF token valid. Request accepted.");
        return true;
    }
}
TEXT
Legitimate form submission:
  Token in form: kR7xP2qM9n... (matches session)
  --> CSRF token valid. Request accepted.

Forged request from evil.com:
  No token (attacker cannot read the bank's page)
  --> CSRF token mismatch. Request rejected.

How does Spring Security handle CSRF?

Spring Security enables CSRF protection by default for session-based applications. It uses the CsrfFilter to generate and validate tokens. For SPAs, the token is exposed via the XSRF-TOKEN cookie (readable by JavaScript because it is not HttpOnly) and echoed in the X-XSRF-TOKEN header.

JAVA
package com.bank.config;

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.web.SecurityFilterChain;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;

@Configuration
public class CsrfConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                // Token readable by SPA JavaScript
                // SPA reads XSRF-TOKEN cookie, sends as X-XSRF-TOKEN header
            );
        return http.build();
    }
}

How does SameSite prevent CSRF?

The SameSite cookie attribute tells the browser not to send the cookie on cross-site requests.

  • SameSite=Strict: cookie never sent on cross-site requests (strongest protection).
  • SameSite=Lax: cookie sent on top-level GET navigations (clicking a link) but not on cross-site POST/form submissions.
TEXT
Set-Cookie: JSESSIONID=abc123; HttpOnly; Secure; SameSite=Strict

Without SameSite:
  evil.com POST to bank.com --> browser sends cookie --> CSRF succeeds

With SameSite=Strict:
  evil.com POST to bank.com --> browser REFUSES to send cookie --> CSRF blocked

When is CSRF not a concern?

CSRF specifically affects cookie-based authentication because cookies are auto-attached. APIs that use the Authorization: Bearer header with a token are not CSRF-prone because browsers do not automatically attach custom headers.

JAVA
// Cookie-based auth: CSRF risk (cookie auto-sent)
// Must use CSRF tokens + SameSite

// Token-based auth: no CSRF risk (header not auto-sent)
// Browser does not auto-attach Authorization header
// Attacker's page cannot set custom headers on cross-site requests

package com.bank.config;

public class WhenToDisableCsrf {
    // Disable CSRF for stateless token-based APIs:
    // http.csrf(csrf -> csrf.disable());
    // Only when ALL authentication is via Authorization header, never cookies!
}

What are common mistakes?

The first mistake is disabling CSRF protection for convenience without understanding the risk, especially on session-based applications.

JAVA
// WRONG: disabling CSRF on a session-based app
http.csrf(csrf -> csrf.disable()); // now vulnerable to CSRF!

// CORRECT: keep CSRF on for session-based, disable only for stateless token-based
http.csrf(csrf -> csrf
    .ignoringRequestMatchers("/api/v1/**") // stateless API with Bearer tokens
);

The second mistake is not validating the token on the server side, or using a predictable token.

How do you combine SameSite and CSRF tokens?

Use both for defense in depth. SameSite blocks most CSRF at the browser level, and CSRF tokens catch any edge cases.

JAVA
// Defense in depth:
// 1. SameSite=Strict on session cookie (browser-level protection)
// 2. CSRF token in forms (application-level protection)
// 3. Verify Origin/Referer header (additional signal)
// 4. GET requests must be idempotent (no state changes on GET)

What should a senior engineer know?

Senior engineers know that CSRF protection is essential for any cookie-based authentication, and that the Origin and Referer headers are useful additional signals but should not be the only defense because they can be absent in some browsers.

Senior engineers also ensure that GET requests never change state, because safe HTTP methods should be idempotent. A state-changing GET is vulnerable to CSRF via image tags and script tags that do not require form submission.

Visual Overview

DIAGRAM
  CSRF Attack:
  ┌──────────┐  visits evil.com   ┌──────────┐
  │  Rajesh  ├───────────────────►│ evil.com │
  │ (logged  │                    │          │
  │  into    │◄───────────────────┤ Hidden   │
  │  bank)   │  auto-submits form │ form     │
  └──────┬───┘  to bank.com       └──────────┘
         │
         │  POST /transfer (cookie auto-attached)
         ▼
  ┌──────────────┐
  │  Bank Server │
  │  Sees valid  │
  │  cookie!     │── No CSRF token ──► REJECT (if protected)
  │              │── Valid CSRF token ──► ALLOW (legitimate)
  └──────────────┘
DIAGRAM
  Defense Layers:
  ┌──────────────────────────────────────┐
  │ Layer 1: SameSite=Strict             │
  │   Browser refuses to send cookie     │
  │   on cross-site requests             │
  ├──────────────────────────────────────┤
  │ Layer 2: CSRF Token                  │
  │   Server rejects requests without    │
  │   the correct unguessable token      │
  ├──────────────────────────────────────┤
  │ Layer 3: Origin/Referer Check        │
  │   Server verifies request origin     │
  ├──────────────────────────────────────┤
  │ Layer 4: Idempotent GETs             │
  │   No state changes on GET requests   │
  └──────────────────────────────────────┘

Key Takeaway

Cross-Site Request Forgery, or CSRF, is an attack that tricks a logged-in user's browser into sending an unwanted, state-changing request to a site where the user is already authenticated. It works because browsers automatically attach cookies to any request to a domain, so a forged request from a malicious page carries the victim's valid session cookie and the server believes it is genuine. The attacker never needs to read the response; the harm is the side effect, like a transfer or a changed email.

In the National Bank of India project, the scenario is Rajesh logged into the bank in one tab while a malicious page in another tab auto-submits a transfer to the attacker. Because the browser attaches his session cookie, the bank would almost execute it. The defense relied on first is the synchronizer token pattern: the bank embeds an unguessable anti-CSRF token in its own pages and requires it on every state-changing request. The attacker's cross-site page cannot read that token because the same-origin policy blocks it, so the forged request is rejected.

The second defense is the SameSite cookie attribute set to Strict or Lax, which tells the browser not to attach the session cookie on cross-site requests at all, neutralizing most CSRF at the browser level. A legitimate transfer with the correct token succeeds, a forged transfer without the token is rejected, and SameSite=Strict refuses to send the cookie cross-site. Both are combined for defense in depth.

The nuance that separates a senior answer is that CSRF specifically affects cookie-based authentication. APIs that send credentials in the Authorization header with a bearer token are not CSRF-prone, because the browser does not automatically attach custom headers.

Remember one sentence: CSRF rides the auto-sent cookie, so block it with an unguessable token the attacker cannot read and SameSite cookies the browser will not send cross-site.

---

Share
Use ← → arrow keys to navigate